classSolution { public: voidmoveZeroes(vector<int>& nums){ auto l = nums.begin(); auto r = nums.begin(); while (r != nums.end()){ if (*r != 0){ int temp = *l; *l = *r; *r = temp; ++l; } ++r;
} } };
11. 盛最多水的容器
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量。说明:你不能倾斜容器。
classSolution(object): defthreeSum(self, nums): """ :type nums: List[int] :rtype: List[List[int]] """ res = [] nums_dict = defaultdict(int) for num in nums: nums_dict[num] += 1 nums_list = list(nums_dict.keys()) nums_list.sort()
if nums_dict.get(0, 0) > 2: res.append([0, 0, 0]) for left in nums_list: if left >= 0: break for right inreversed(nums_list): if right <= 0: break medium = -left-right if medium in nums_dict: if medium == left or medium == right: if nums_dict[medium] > 1: res.append([left, medium, right]) elif medium > left and medium < right: res.append([left, medium, right]) return res
classSolution { public: vector<vector<int>> threeSum(vector<int>& nums) { vector<vector<int>> res = {}; sort(nums.begin(),nums.end()); auto bg = nums.begin(); auto ed = nums.end(); for (auto p = bg; p<ed-2;){ auto l = p+1, r = ed - 1; int k = *p; while(l < r){ while(*l+*r<-k&&l<r) l++; while(*l+*r>-k&&l<r) r--; if(l>=r) break; if (*l+*r==-k){ res.push_back({*p,*l,*r}); int x=*l,y=*r; while(*l==x&&l<r)l++; while(*r==y&&l<r)r--; } } while(*p==k&&p<ed-2)p++; } return res; } };
42. 接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
解法:每个柱子上存储的雨量由leftmax和rightmax较低者决定,双指针向中间走,不断更新leftmax和rightmax。对于left,leftmax一定准确;对于right,rightmax一定准确。故只需判断当前leftmax和rightmax的大小,即可判断是更新left还是right准确。更新后调整索引位置变化1即可,时间复杂度O(n),空间复杂度O(1)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
classSolution(object): deftrap(self, height): """ :type height: List[int] :rtype: int """ l,r=0,len(height)-1 lmax,rmax=height[0],height[-1] ans = 0 while l<r: if lmax<rmax: ans+=lmax-height[l] l+=1 lmax=max(lmax,height[l]) else: ans+=rmax-height[r] r-=1 rmax = max(rmax,height[r]) return ans
classSolution(object): deflengthOfLongestSubstring(self, s): """ :type s: str :rtype: int """ occ =set() l,r=0,0 k=0 while r < len(s): if s[r] in occ: while s[r] in occ: occ.remove(s[l]) l+=1 occ.add(s[r]) r+=1 k=max(k,r-l) return k
classSolution { public: vector<int> findAnagrams(string s, string p){ int slen=s.size(),plen=p.size(); if (slen<plen){ returnvector<int>(); } vector<int> ans; vector<int> count(26); for (int i=0;i<plen;++i){ ++count[s[i]-'a']; --count[p[i]-'a']; } int differ = 0; for (int i=0;i<26;++i){ if (count[i]!=0){ ++differ; } } if (differ==0){ ans.emplace_back(0); } for (int i=0;i<slen-plen;++i){ //窗口少了字母s[i],引起变化,更新count和differ --count[s[i]-'a']; if (count[s[i]-'a']==0){ --differ; } elseif (count[s[i]-'a']==-1){ ++differ; }
//窗口多了字母s[i+p],引起变化,更新count和differ ++count[s[i+plen]-'a']; if (count[s[i+plen]-'a']==1){ ++differ; } elseif (count[s[i+plen]-'a']==0){ --differ; } if (differ == 0){ ans.emplace_back(i+1); } } return ans;
} };
子串
560. 和为 K 的子数组
给你一个整数数组 nums 和一个整数 k ,请你统计并返该数组中和为k的子数组的个数 。子数组是数组中元素的连续非空序列。
解法:暴力枚举会超时,因此可以在遍历时统计nums[:j]的和sum,使用sum[j]-k=sum[i]来判断是否存在nums[j:i]的和为k的子数组,数量为sum[j]-k出现的次数。时间复杂度由O(n^2)变为O(n),空间复杂度由O(1)变为O(n)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
classSolution(object): defsubarraySum(self, nums, k): """ :type nums: List[int] :type k: int :rtype: int """ prefix_count = {0: 1} # 储存前缀和出现的次数 current_sum = 0# 存储当前前缀和 count = 0# 统计和为k的子数组数量 for num in nums: current_sum += num if current_sum - k in prefix_count: count += prefix_count[current_sum - k] prefix_count[current_sum] = prefix_count.get(current_sum, 0) + 1 return count
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
classSolution { public: intsubarraySum(vector<int>& nums, int k){ unordered_map<int,int> mp; mp[0]=1; int ans=0,pre=0; for (auto &num:nums){ pre += num; if (mp.find(pre-k) != mp.end()){ ans +=mp[pre-k]; } mp[pre]++;
} return ans; } };
239. 滑动窗口最大值
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回 滑动窗口中的最大值 。
解法: 滑动过程中只需用一个deque在队首维护最大值即可,队列里不需要完整的排序。只需保证:新元素加入时,删除所有比它小的“无用元素”;确保窗口滑动时,队首如果过期就弹出。
from collections import deque classSolution: defmaxSlidingWindow(self, nums: List[int], k: int) -> List[int]: res=[] n = len(nums) q = deque() for i inrange(k): while q and nums[q[-1]]<=nums[i]: q.pop() q.append(i) res.append(nums[q[0]]) for i inrange(k,n): while q and nums[q[-1]]<=nums[i]: q.pop() q.append(i) while q[0] <= i-k: q.popleft() res.append(nums[q[0]]) return res
classSolution(object): defmaxSubArray(self, nums): """ :type nums: List[int] :rtype: int """ t=0 ma=float("-inf") for i in nums: t=t+i if t>ma: ma=t if t<0: t=0 return ma
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
classSolution { public: intmaxSubArray(vector<int>& nums){ int t=0,ma=std::numeric_limits<int>::min(); for(auto & num: nums){ t+=num; if (t>ma){ ma=t; } if (t<0){ t=0; } } return ma; } };
C++中可以用 std::sort(v.begin(), v.end(), cmp) 排序,默认按 < 运算符排序,也可自定义 cmp 函数。
C++ 匿名函数可用 [](const int& a, const int& b) { return a < b; } 表示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
classSolution(object): defmerge(self, intervals): """ :type intervals: List[List[int]] :rtype: List[List[int]] """ intervals.sort(key=lambda x:x[0]) ans = [] for i,j in intervals: ifnot ans or ans[-1][1] < i: ans.append([i,j]) else: ans[-1][1]=max(ans[-1][1],j) return ans
classSolution(object): defrotate(self, nums, k): """ :type nums: List[int] :type k: int :rtype: None Do not return anything, modify nums in-place instead. """ n= len(nums) k = k % n # 防止 k 大于 n self.reverse(nums,0,n-1) self.reverse(nums,0,k-1) self.reverse(nums,k,n-1) defreverse(self,nums,start,end): while start<end: nums[start],nums[end]=nums[end],nums[start] start+=1 end-=1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
classSolution { public: voidrotate(vector<int>& nums, int k){ k %= nums.size(); reverse(nums, 0, nums.size() - 1); reverse(nums, 0, k - 1); reverse(nums, k, nums.size() - 1); } voidreverse(vector<int>& nums, int start, int end){ while (start < end) { swap(nums[start], nums[end]); start += 1; end -= 1; } } };
classSolution(object): defproductExceptSelf(self, nums): """ :type nums: List[int] :rtype: List[int] """ a = [0]*len(nums) b= [0]*len(nums) a[0]=nums[0] b[-1]=nums[-1] for i inrange(1,len(nums)): a[i]=a[i-1]*nums[i] for i inrange(len(nums)-2,-1,-1): b[i]=b[i+1]*nums[i] return [b[1]]+[a[i-1]*b[i+1] for i inrange(1,len(nums)-1)]+[a[-2]]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
classSolution { public: vector<int> productExceptSelf(vector<int>& nums){ int n = nums.size(); vector<int> ans(n); ans[0]=nums[0]; for (int i=1;i<n;++i){ ans[i]=ans[i-1]*nums[i]; } int r=1; for (int i=n-1;i>0;--i){ ans[i]=ans[i-1]*r; r*=nums[i]; } ans[0]=r; return ans; } };
classSolution(object): deffirstMissingPositive(self, nums): """ :type nums: List[int] :rtype: int """ n=len(nums) for i inrange(len(nums)): if nums[i] <1: nums[i]=n+1 for i inrange(len(nums)): a = abs(nums[i]) if a<n+1: nums[a-1]=-abs(nums[a-1]) for i inrange(len(nums)): if nums[i]>0: return i+1 return n+1
1 2 3 4 5 6 7 8 9 10 11 12 13 14
classSolution { public: intfirstMissingPositive(vector<int>& nums){ for(int i = 0; i < nums.size(); i++){ while(nums[i]>=1 && nums[i] <=nums.size() &&nums[i] != nums[nums[i] - 1] ){ swap(nums[i], nums[nums[i] - 1]); } } for(int i = 0; i < nums.size(); i++){ if(nums[i] != i + 1) return i + 1; } return nums.size() + 1; } };
矩阵
73. 矩阵置零
给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用原地算法。
解法:遍历矩阵,记录元素为0的行和列,然后再遍历一次置为0.如果使用矩阵第一行和第一列进行记录,使用额外变量记录第一行或者第一列是否含0,则空间复杂度降为O(1).
classSolution(object): defsetZeroes(self, matrix): """ :type matrix: List[List[int]] :rtype: None Do not return anything, modify matrix in-place instead. """ m,n=len(matrix),len(matrix[0]) temp = [] for i inrange(m): for j inrange(n): if matrix[i][j]==0: temp.append((i,j)) for i, j in temp: matrix[i][:] = [0] * n # 设置第 i 行全为 0 for row in matrix: row[j] = 0
classSolution { public: voidsetZeroes(vector<vector<int>>& matrix){ int m=matrix.size(),n=matrix[0].size(); vector<vector<int>> temp; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { if (!matrix[i][j]) { temp.push_back({i,j}); } } } for (auto& v:temp) { for (int i = 0; i < m; ++i){ matrix[i][v[1]]=0; }
for (int i = 0; i < n; ++i){ matrix[v[0]][i]=0; } } } };
54. 螺旋矩阵
给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
解法:
1.模拟贪吃蛇,遇到边界就顺时针转向,同时将已经探索的节点标记为边界,空间复杂度O(mn)
2.从外层内缩、逐层遍历,每一层从左上角顺时针绕圈遍历,空间复杂度O(1),推荐
classSolution(object): defspiralOrder(self, matrix): """ :type matrix: List[List[int]] :rtype: List[int] """ ans = [] left,right,top,bottom=0,len(matrix[0])-1,0,len(matrix)-1 while left<=right and top <= bottom: for column inrange(left,right+1): ans.append(matrix[top][column]) for row inrange(top+1,bottom+1): ans.append(matrix[row][right]) if left < right and top < bottom: for column inrange(right-1,left,-1): ans.append(matrix[bottom][column]) for row inrange(bottom,top,-1): ans.append(matrix[row][left]) top+=1 bottom-=1 left+=1 right-=1 return ans
int rows = matrix.size(), columns = matrix[0].size(); vector<int> order; int left = 0, right = columns - 1, top = 0, bottom = rows - 1; while (left <= right && top <= bottom) { for (int column = left; column <= right; column++) { order.push_back(matrix[top][column]); } for (int row = top + 1; row <= bottom; row++) { order.push_back(matrix[row][right]); } if (left < right && top < bottom) { for (int column = right - 1; column > left; column--) { order.push_back(matrix[bottom][column]); } for (int row = bottom; row > top; row--) { order.push_back(matrix[row][left]); } } left++; right--; top++; bottom--; } return order; } };
48. 旋转图像
给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
解法:
1.每个元素直接相对中心旋转90度,临时变量存储覆盖掉的值,从左上角开始旋转,经过四次旋转,最终替换掉的值将回到左上角。只需遍历四分之一的区域,即可覆盖所有元素的旋转。技巧第i行j列的元素将旋转到倒数第i列(正数n-i-1列)第j行的位置即(i,j)->(j,n-i-1),取左上角区域n/2 x (n+1)/2 .
2.每个元素直接相对中心旋转90度等效于上下翻转一次+沿着主对角线翻转一次,可推广至所有元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
classSolution(object): defrotate(self, matrix): """ :type matrix: List[List[int]] :rtype: None Do not return anything, modify matrix in-place instead. """ n = len(matrix) #上下翻转 for i inrange(n//2): for j inrange(n): matrix[i][j], matrix[n - i - 1][j] = matrix[n - i - 1][j], matrix[i][j] # 主对角线翻转 for i inrange(n): for j inrange(i): matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
classSolution { public: voidrotate(vector<vector<int>>& matrix){ int n= matrix.size(); for (int i = 0; i < n / 2; ++i) { for (int j = 0; j < (n + 1) / 2; ++j) { // int temp = matrix[i][j]; // swap(temp,matrix[j][n-i-1]); // swap(temp,matrix[n-i-1][n-j-1]); // swap(temp,matrix[n-j-1][i]); // matrix[i][j]=temp; int temp = 0,x=j,y=i; for (int k=0;k<5;++k){ swap(temp,matrix[y][x]); swap(x,y); x=n-x-1; } } } } };
240. 搜索二维矩阵 II
编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:每行的元素从左到右升序排列。每列的元素从上到下升序排列。
解法:从右上角开始,如果当前值大于taget,排除当前列向左移动;如果当前值小于target,排除当前行向下移动;如果当前值等于target,返回true。如果矩阵遍历完仍未找到,返回false。
m, n = len(matrix), len(matrix[0]) # 从右上角开始查找 i, j = 0, n - 1 while i < m and j >= 0: if matrix[i][j] == target: returnTrue elif matrix[i][j] > target: j -= 1# 排除当前列 else: i += 1# 排除当前行 returnFalse
classSolution { public: boolsearchMatrix(vector<vector<int>>& matrix, int target){ int m = matrix.size(); int n = matrix[0].size(); int i = 0, j = n - 1;
// 从右上角开始搜索 while (i < m && j >= 0) { if (matrix[i][j] == target) { returntrue; } elseif (matrix[i][j] > target) { --j; // 排除当前列 } else { ++i; // 排除当前行 } }
classSolution(object): defisPalindrome(self, head): """ :type head: Optional[ListNode] :rtype: bool """ #计数或者快慢指针找到一半的位置 n = 0 current = head while current: n+=1 current = current.next # 翻转前半部分 current = head last,next = None,None for i inrange(0,n//2): next = current.next current.next=last last = current current=next # 判断是否回文 if n%2: l,r=last,current.next else: l,r=last,current ans = True while l and r: if l.val != r.val: ans = False break l,r=l.next,r.next # 还原链表 last,next = current,None current = last while current: next = current.next current.next = last last = current current = next return ans
classSolution(object): defhasCycle(self, head): """ :type head: ListNode :rtype: bool """ p = q = head while q and q.next: p = p.next q = q.next.next if p == q: returnTrue returnFalse
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
classSolution { public: boolhasCycle(ListNode *head){ ListNode* slow = head; ListNode* fast = head; while (fast && fast->next){ slow=slow->next; fast=fast->next->next; if (fast == slow){ returntrue; } } returnfalse; } };
classSolution(object): defdetectCycle(self, head): """ :type head: ListNode :rtype: ListNode """ p = q = head while q and q.next: p = p.next q = q.next.next if p == q: q=head while q != p: q=q.next p = p.next return q returnNone
classSolution(object): defremoveNthFromEnd(self, head, n): """ :type head: Optional[ListNode] :type n: int :rtype: Optional[ListNode] """ fast = head target = head
# 提前走 n + 1 步 for _ inrange(n + 1): if fast: fast = fast.next else: # 如果 fast 提前走不了 n+1 步,说明要删的是第一个节点 return head.next
# fast 与 target 同时前进,直到 fast 到尾部 while fast: target = target.next fast = fast.next
classSolution(object): defreverseKGroup(self, head, k): """ :type head: Optional[ListNode] :type k: int :rtype: Optional[ListNode] """ n=0 temp=head while temp: n+=1 temp=temp.next if n==k: break if n<k: return head current =head last,next=None,None for i inrange(k): next=current.next current.next =last last = current current = next head.next=self.reverseKGroup(current,k) return last
classSolution: defcopyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]': ifnot head: returnNone mp ={} p = head while p: mp[p]=Node(p.val) p = p.next p = head while p: if p.next: mp[p].next=mp[p.next] if p.random: mp[p].random=mp[p.random] p = p.next return mp[head]
classSolution: defsortList(self, head: Optional[ListNode]) -> Optional[ListNode]: k =1 n = 0 h=head while h: n+=1 h = h.next res = ListNode(0) res.next = head while k < n: pre,current=res,res.next while current: h1,i= current,k while i and current: current = current.next i -=1 if i: break h2,i= current,k while current and i: i-=1 current = current.next k1,k2=k,k-i while k1 or k2: if k1==0: pre.next = h2 k2-=1 h2 = h2.next elif k2==0: pre.next = h1 k1-=1 h1=h1.next elif h1.val<h2.val: pre.next = h1 k1-=1 h1=h1.next else: pre.next = h2 k2-=1 h2 = h2.next pre = pre.next pre.next = current k*=2 return res.next
classSolution(object): defmergeKLists(self, lists): """ :type lists: List[Optional[ListNode]] :rtype: Optional[ListNode] """ heap = [] for i in lists: if i: heapq.heappush(heap,(i.val,i)) dummy = ListNode() ans = dummy while heap: f = heapq.heappop(heap) ans.next = f[1] ans = ans.next if f[1].next: heapq.heappush(heap,(f[1].next.val,f[1].next)) return dummy.next
/** * Your LRUCache object will be instantiated and called as such: * LRUCache* obj = new LRUCache(capacity); * int param_1 = obj->get(key); * obj->put(key,value); */
classSolution { public: int ans; intdiameterOfBinaryTree(TreeNode* root){ ans =0; depth(root); return ans; } intdepth(TreeNode* node){ if (!node){ return0; } int l = depth(node->left); int r = depth(node->right); ans = max(ans,l+r); returnmax(l,r)+1; } };
classSolution: defrightSideView(self, root: Optional[TreeNode]) -> List[int]: ifnot root: return [] layer = [root] ans = [] while layer: ans.append(layer[-1].val) temp = [] for node in layer: if node.left: temp.append(node.left) if node.right: temp.append(node.right) layer=temp return ans
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
解法:
1.遍历每一个节点下的子树是否包含p,q,返回最深的节点,但这样会造成大量重复搜索
2.递归:p,q在当前root的两侧或者一侧。当两侧子树分别包含p和q时,当前节点为最近公共祖先。当一侧确定不包含p和q时即没有公共祖先,另一侧必定包含p和q,递归遍历找这一侧的最近公共祖先。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
classSolution(object): deflowestCommonAncestor(self, root, p, q): """ :type root: TreeNode :type p: TreeNode :type q: TreeNode :rtype: TreeNode """ ifnot root or root==p or root==q: return root left = self.lowestCommonAncestor(root.left,p,q) right=self.lowestCommonAncestor(root.right,p,q) if left and right :return root if left:return left if right:return right
classSolution: defnumIslands(self, grid: List[List[str]]) -> int: ans=0 m,n=len(grid),len(grid[0]) for y inrange(m): for x inrange(n): if grid[y][x]=='1': ans+=1 self.bfs(grid,m,n,x,y) return ans defbfs(self,grid,m,n,x,y): q=deque() q.append((x,y)) while q: x,y=q.popleft() if grid[y][x]!='1':continue grid[y][x]='-' if x>0: q.append((x-1,y)) if x<n-1: q.append((x+1,y)) if y>0: q.append((x,y-1)) if y<m-1: q.append((x,y+1))
def__init__(self): self.words={} definsert(self, word: str) -> None: words = self.words for c in word: if c notin words: words[c]={} words = words[c] words['']=None
defsearch(self, word: str) -> bool: words =self.words for c in word: if c notin words: returnFalse words=words[c] returnTrueif''in words elseFalse
defstartsWith(self, prefix: str) -> bool: words =self.words for c in prefix: if c notin words: returnFalse words=words[c] returnTrue
n = len(digits) k = 1 for i in digits: k *= len(phoneMap[i])
out = [''for _ inrange(k)]
temp = k for i inrange(n): idx = 0 temp = temp // len(phoneMap[digits[i]]) while idx < k: for s in phoneMap[digits[i]]: for _ inrange(temp): out[idx] += s idx += 1 return out
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
解法:回溯法,递归填入第index个字符,向四周搜索是否有满足条件的字符,并标记已经访问过的位置。终止条件:board[i][j]!=word[index]或者index+1==word.size()
// // 自定义哈希函数 // struct pair_hash { // template <typename T1, typename T2> // size_t operator (const pair<T1,T2>& p) const { // auto h1 = hash<T1>{}(p.first); // 哈希第一个元素 // auto h2 = hash<T2>{}(p.second); // 哈希第二个元素 // return h1 ^ h2; // 通过异或组合哈希值 // } // }; classSolution { private: vector<vector<int>> di = { {-1, 0}, {1, 0}, {0, -1}, {0, 1} }; bool isExist = false; int m, n; // unordered_set<pair<int,int>,pair_hash> visited; public: boolexist(vector<vector<char>>& board, string word){ m = board.size(), n = board[0].size(); for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (board[i][j] == word[0]) { backtrack(board, 0, word, i, j); if (isExist) returntrue; } } } returnfalse; }
voidbacktrack(vector<vector<char>>& board, int index, string& word, int i, int j){ // if (visited.count({i,j}))return; if (board[i][j] != word[index])return; if (index + 1 == word.length()) { isExist = true; } else { auto board_val = board[i][j]; board[i][j] = '0'; // visited.insert({i,j}); for (int d = 0; d < 4; d++) { int newi = i + di[d][0]; int newj = j + di[d][1]; if (newi >= 0 && newi < m && newj >= 0 && newj < n) { backtrack(board, index + 1, word, newi, newj); } } board[i][j] = board_val; // visited.erase({i,j}); } } };
131. 分割回文串
给你一个字符串 s,请你将 s 分割成一些 子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
解法:使用动态规划求解dp[i][j]表示s[i:j+1]是否为回文串,再使用回溯法求解所有可能的分割方案。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入
解法:递归或者显式维护一个栈,递归返回[]内的结果,并乘以记录的次数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
classSolution: defdecodeString(self, s: str) -> str: stack, res, multi = [], "", 0 for c in s: if c == '[': stack.append([res,multi]) res,multi="",0 elif c == ']': last_res,cur_multi=stack.pop() res=last_res+cur_multi*res elif'0' <= c <= '9': multi = multi * 10 + int(c) else: res += c return res
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
解法:
1.对于每一个柱子i,包含该柱子在内的最大矩形的左右边界为第一个高度小于height[i]的柱子。可以正反序遍历两次,求得每根柱子左极限和右极限,然后求最大值
2.使用单调递增栈,从左向右遍历,遇到高度小于栈顶柱子的柱子,即可求出包含栈顶柱子的最大矩形面积。弹出栈顶元素,可继续比较求解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
classSolution: deflargestRectangleArea(self, heights: List[int]) -> int: left,right=[0]*len(heights),[0]*len(heights) left[0]=-1 right[-1]=len(heights) for i inrange(len(heights)): temp=i-1 while temp>=0and heights[temp]>=heights[i]: temp=left[temp] left[i]=temp for i inrange(len(heights)-2,-1,-1): temp=i+1 while temp<len(heights) and heights[temp]>=heights[i]: temp=right[temp] right[i]=temp ans=0 for i inrange(len(heights)): ans=max(ans,(right[i]-left[i]-1)*heights[i]) return ans
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
classSolution { public: intlargestRectangleArea(vector<int>& heights){ int ans=0; stack<int> stack; heights.push_back(0); heights.insert(heights.begin(),0); for(int i=0;i<heights.size();++i){ while (!stack.empty() && heights[i]<heights[stack.top()]){ int temp = stack.top();stack.pop(); ans=max(ans,(i-stack.top()-1)*heights[temp]); } stack.push(i); } return ans; } };
for num in nums: iflen(heap) < k: heap.append(num) #上浮该元素 i=len(heap)-1 parent =(i-1)//2 while i>0and heap[parent]>heap[i]: heap[parent],heap[i]=heap[i],heap[parent] i=parent parent=(i-1)//2 else: if num > heap[0]: #下浮该元素 heap[0] = num i=0 left=2*i+1 right=2*i+2 whileTrue: small=i if left<len(heap) and heap[left]<heap[small]: small=left if right<len(heap) and heap[right]<heap[small]: small=right if small==i: break heap[i],heap[small]=heap[small],heap[i] i=small left=2*i+1 right=2*i+2
classSolution { public: intquickselect(vector<int> &nums, int l, int r, int k){ if (l == r) return nums[k]; int partition = nums[l], i = l - 1, j = r + 1; while (i < j) { do i++; while (nums[i] < partition); do j--; while (nums[j] > partition); if (i < j) swap(nums[i], nums[j]); } if (k <= j)returnquickselect(nums, l, j, k); elsereturnquickselect(nums, j + 1, r, k); }
intfindKthLargest(vector<int> &nums, int k){ int n = nums.size(); returnquickselect(nums, 0, n - 1, n - k); } };
classSolution { public: intjump(vector<int>& nums){ if (nums.size()==1)return0; int step=0; int max_distance=0; int next_max_distance=0; for (int i=0;i<nums.size();++i){ next_max_distance = max(next_max_distance,i+nums[i]);//假如从当前位置起跳可以到达的最大位置,但目前没有采纳; if(i==max_distance){//到达当前步数的极限,必须从过去选择一个位置跳一次来更新极限。 step++; max_distance=next_max_distance; if (max_distance>=nums.size()-1)break; } } return step; } };
763. 划分字母区间
给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。例如,字符串 “ababcc” 能够被分为 [“abab”, “cc”],但类似 [“aba”, “bcc”] 或 [“ab”, “ab”, “cc”] 的划分是非法的。
注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s 。
返回一个表示每个字符串片段的长度的列表。
解法:遍历字符串,统计每个字母出现的最后位置。同一字母需要分在同一片段,故该片段的start和end必然在该字母第一次出现和最后一次出现的位置外侧。维护一个当前片段start和end,根据字母i的最后出现的位置扩展end直到i==end,得到一个符合要求的片段,长度为end-start+1.再求下一个片段的长度,直到遍历完字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
classSolution: defpartitionLabels(self, s: str) -> List[int]: last={} for i,ch inenumerate(s): last[ch]=i res = list() start=end=0 for i,ch inenumerate(s): end=max(end,last[ch]) if i==end: res.append(end-start+1) start=end+1 end=start return res
动态规划
70. 爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
解法:dp[i] 表示爬到第 i 阶的方法数,则 dp[i] = dp[i-1] + dp[i-2]。
注意这里用递归会超时,递归没有记忆化,会大量重复结算,所以用数组存储中间结果。
1 2 3 4 5 6 7 8
classSolution: defclimbStairs(self, n: int) -> int: dp=[0]*(n+1) dp[0]=1 dp[1]=1 for i inrange(2,n+1): dp[i]=dp[i-1]+dp[i-2] return dp[n]
classSolution: defgenerate(self, numRows: int) -> List[List[int]]: res=[[1]] for row inrange(1,numRows): temp=[1]*(row+1) for i inrange(1,row): temp[i]=res[-1][i-1]+res[-1][i] res.append(temp) return res
classSolution: defcoinChange(self, coins: List[int], amount: int) -> int: f=[0]*(amount+1) for i inrange(1,amount+1): mint=float("inf") for c in coins: if i-c>=0and f[i-c]!=-1: mint=min(mint,f[i-c]) if mint==float("inf"): f[i]=-1 else: f[i]=1+mint return f[amount]
139. 单词拆分
给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true。
注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
解法:遍历字符串s,判断s[:j]是否可以被字典中的单词拼接出,利用历史s[:i]的结果加上s[i:j]是否在字典中即可判断。
1 2 3 4 5 6 7 8 9 10
classSolution: defwordBreak(self, s: str, wordDict: List[str]) -> bool: n=len(s) f=[False]*(n+1) f[0]=True for i inrange(n): for j inrange(i+1,n+1): if (f[i] and s[i:j] in wordDict): f[j]=True return f[n]
classSolution: deflengthOfLIS(self, nums: List[int]) -> int: f=[1]*(len(nums)+1) f[0]=0 for i inrange(1,len(nums)+1): maxv=0 for j inrange(i-1): if nums[j]<nums[i-1]: maxv=max(maxv,f[j+1]) f[i]=1+maxv returnmax(f)
classSolution: defcanPartition(self, nums: List[int]) -> bool: s = sum(nums) if s % 2 != 0: returnFalse t = s // 2 dp = [False] * (t + 1) dp[0] = True# 空集和为 0,是基础状态 for num in nums: for j inrange(t, num - 1, -1):#倒序,防止提前覆盖上一轮的结果 dp[j] = dp[j] or dp[j - num] return dp[t]
classSolution { public: intuniquePaths(int m, int n){ vector<vector<int>> dp(m,vector<int>(n)); for(int i=0;i<m;++i){ for (int j=0;j<n;++j){ if (i==0 || j==0 )dp[i][j]=1; else dp[i][j]=dp[i-1][j]+dp[i][j-1]; } } return dp[m-1][n-1]; } };
64. 最小路径和
给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
解法:动态规划,dp[i][j]表示从(0,0)走到(i,j)的最小路径和,则dp[i][j] = min(dp[i-1][j],dp[i][j-1])+grid[i][j].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
classSolution { public: intminPathSum(vector<vector<int>>& grid){ int m=grid.size(),n=grid[0].size(); vector<vector<int>> dp(m,vector<int>(n)); for(int i=0;i<m;++i){ for (int j=0;j<n;++j){ if (i==0 && j==0)dp[i][j]=grid[i][j]; elseif(i==0)dp[i][j]=dp[i][j-1]+grid[i][j]; elseif(j==0)dp[i][j]=dp[i-1][j]+grid[i][j]; else dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j]; } } return dp[m-1][n-1]; } };
5. 最长回文子串
给你一个字符串 s,找到 s 中最长的 回文 子串。
解法:
1.Manacher 算法,将字符串变为“#ab#bc#”的形式,这样可以将奇偶字符串统一处理。使用中心扩散法记录回文中心i的半径p[i],并维护一个最大回文边界right_max和对应的中心center.遍历计算p[i]时,根据回文对称性,如果i>=right_max时只能中心扩散计算,当i小于right_max时,可以利用上历史数据:设p关于center的对称位置mirror=2*center-i,当right_max-i>p[mirror]时,p[i]等于p[mirror]+2;当right_max-i==p[mirror]时,p[i]可以从right_max-i开始扩散;当right_max-i小于p[mirror]时,p[i]等于right_max-i.整个过程中维护最大串的长度和中心位置。
2.动态规划,dp[i][j]表示s[i:j+1]是否为回文串,则dp[i][j] = (s[i]==s[j]) and (i+1>=j-1 or dp[i+1][j-1]),注意边界条件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
classSolution: deflongestPalindrome(self, s: str) -> str: t = "#".join("^" + s + "$") halfLen = [0] * len(t) max_i = box_r=box_center= 0 for i inrange(2, len(t) - 2): hl = 1 if i < box_r: hl = min(halfLen[2*box_center-i], box_r - i + 1) while t[i - hl] == t[i + hl]: hl += 1 box_r += 1 box_center=i halfLen[i] = hl if hl > halfLen[max_i]: max_i = i max_hl = halfLen[max_i] return s[(max_i - max_hl) // 2 : (max_i + max_hl) // 2 - 1]
classSolution: defnextPermutation(self, nums: List[int]) -> None: """ Do not return anything, modify nums in-place instead. """ x=-1 for i inrange(len(nums)-1): if nums[i]<nums[i+1]: x=i if x==-1: nums.sort() return y=x+1 for i inrange(y+1,len(nums)): if nums[x]<nums[i]<nums[y]: y=i nums[x],nums[y]=nums[y],nums[x] nums[x+1:]=sorted(nums[x+1:])