reverse ListNode
步步拆解:如何递归地反转链表的一部分 - 反转链表 II - 力扣(LeetCode)
1️⃣翻转整个链表
难度简单2760
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public ListNode reverseList (ListNode head) { if (head==null ||head.next==null ) return head; ListNode pre=null ; ListNode cur=head; while (cur!=null ){ ListNode tmp=cur.next; cur.next=pre; pre=cur; cur=tmp; } return pre; } }
递归翻转链表
1 2 3 4 5 6 7 ListNode reverse (ListNode head) { if (head.next == null ) return head; ListNode last = reverse(head.next); head.next.next = head; head.next = null ; return last; }
具体来说,我们的 reverse
函数定义是这样的:
输入一个节点 head
,将「以 head 为起点」的链表反转 ,并返回反转之后的头结点 。
明白了函数的定义,在来看这个问题。比如说我们想反转这个链表:
那么在我们输入reverse(head)
后 , 会在这里递归
ListNode last = reverse(head.next);
这里不要去想递归的过程,
简单的递归还可以去想想, 复杂的递归凭空想象难以进行
我们直接来看递归的结果
结果:
再来看reverse
的定义, 会在返回链表之后返回函数的头结点, 也就是上面的last
现在再来看下面的代码:
head.next.next = head;
接下来:
head.next = null; return last;
这样一来,我们可以发现整个链表已经翻转过来了
1 2 3 4 5 6 7 ListNode reverseListNode (ListNode head) { if (head.next==null ) return null ; ListNode last=reverseListNode(head.next); head.next.next=head; head.next=null ; return last; }
2️⃣反转链表前 N 个节点
问题:给定链表头结点 n, 翻转链表前n个节点
翻转前n个节点, 我们需要多做的一步就是让最开始的 head
指向第n+1
个节点
那么我们需要做的操作就是
保存第n+1
个节点
让最开始的头结点指向第n+1
个节点
这里可以在递归终止的时候去保存第n+1
个节点
1 2 3 4 5 6 7 8 9 10 11 12 ListNode succerror=null ; ListNode reverseListNode (ListNode head,int n) { if (n==1 ){ succerror=head.next; return head; } ListNode last=reverseListNode(head.next,n-1 ); head.next.next=head; head.next=succerror; return last; }z`
3️⃣翻转指定范围的节点
经过了上面的解释, 那么对于当前的这个问题, 也就迎刃而解了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { ListNode succerror=null ; public ListNode reverseBetween (ListNode head, int left, int right) { if (left==1 ){ return reverseListNode(head,right); } head.next=reverseBetween(head.next,left-1 , right-1 ); return head; } ListNode reverseListNode (ListNode head,int n) { if (n==1 ){ succerror=head.next; return head; } ListNode last=reverseListNode(head.next,n-1 ); head.next.next=head; head.next=succerror; return last; } }
难度中等1397
给你单链表的头指针 head
和两个整数 left
和 right
,其中 left <= right
。请你反转从位置 left
到位置 right
的链表节点,返回 反转后的链表 。
迭代
把链表的节点储存在List
中, 交换节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public ListNode reverseBetween (ListNode head, int left, int right) { if (head.next==null ||left==right) return head; ListNode cur=head; List<ListNode> list=new ArrayList <>(); list.add(new ListNode ()); while (cur!=null ){ list.add(cur); cur=cur.next; } int len=right-left; for (int i = 0 ; i < (len+1 )/2 ; i++) { int tmp=list.get(right-i).val; list.get(right-i).val=list.get(left+i).val; list.get(left+i).val=tmp; } return head; } }
难度中等2131
给你一个整数数组 coins
,表示不同面额的硬币;以及一个整数 amount
,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1
。
你可以认为每种硬币的数量是无限的。
dp数组含义 : dp[j]:凑足总额为j所需钱币的最少个数为dp[j]
确定递推公式
得到dp[j](考虑coins[i]),只有一个来源,dp[j - coins[i]]
(没有考虑coins[i])。
凑足总额为j - coins[i]
的最少个数为dp[j - coins[i]]
,
那么只需要加上一个钱币coins[i]
即dp[j - coins[i]] + 1
就是dp[j]
(考虑coins[i])
所以dp[j]
要取所有 dp[j - coins[i]] + 1
中最小的。
递推公式:dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
可以大致理解为 :
遍历硬币的金额, 其实就是物品, amount
就是背包的容量
当前需要的金额大于金币的面值, 也就是可以装金币
那么如果装了当前的金币, 剩余的需要的面值就是 j-coins[i]
,
这个时候我们就需要dp[j-coins[i]] dp[j] = Math.min(dp[j],dp[j-coins[i]]+1);
对于替换较小金币为较大金币的问题, 可以在遍历金币的过程中更新对应的数值
dp数组初始化 :
首先凑足总金额为0所需钱币的个数一定是0,那么dp[0] = 0;
考虑到递推公式的特性,dp[j]
必须初始化为一个较大的数字,
否则就会在min(dp[j - coins[i]] + 1, dp[j])
比较的过程中被初始值覆盖。所以下标非0的元素都是应该是最大值。
这里将数组除了0之外的元素初始化为一个特殊的元素即可 114514
返回值 : 题目要求计算返回可以凑成总金额所需的 最少的硬币个数 , 因此我们返回dp[amount]
即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int coinChange (int [] coins, int amount) { int []dp=new int [amount+1 ]; Arrays.fill(dp,114514 ); dp[0 ]=0 ; for (int i = 0 ; i < coins.length; i++) { for (int j = coins[i]; j <= amount; j++) { if (dp[j-coins[i]]!=114514 ){ dp[j] = Math.min(dp[j],dp[j-coins[i]]+1 ); } } } return dp[amount]==114514 ?-1 :dp[amount]; } }
难度中等919
给你一个整数数组 coins
表示不同面额的硬币,另给一个整数 amount
表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0
。
假设每一种面额的硬币有无限个。
题目数据保证结果符合 32 位带符号整数。
dp数组含义 : dp[j]
表示可以凑成j
金额 的方法的种数
状态转移 : dp[j]
(考虑coins[i]的组合总和) 就是所有的dp[j - coins[i]]
(不考虑coins[i])相加。
dp[j]+=dp[j-coins[i]]
求装满背包有几种方法,一般公式都是:dp[j] += dp[j - nums[i]];
dp数组初始化: 对于 0 ,根据dp数组的含义应该初始化为1, 对于其他的位置, 应该初始化为0,
返回值 : 题目要求计算并返回可以凑成总金额的硬币组合数 , 因此我们返回dp[amount]
即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int change (int amount, int [] coins) { int []dp=new int [amount+1 ]; dp[0 ]=1 ; for (int i = 0 ; i < coins.length; i++) { for (int j = coins[i]; j <=amount; j++) { dp[j]+=dp[j-coins[i]]; } } return dp[amount]; } }
主要注意的是, 本题必须要先遍历物品, 在遍历容量, 如果先遍历容量的话dp[i] 表示的就是 凑成 i 金额 的方法的方法数 (组合数)
组合不强调元素之间的顺序,排列强调元素之间的顺序 。
可以看下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Test public void printTest () { changeTest(5 ,new int []{1 ,2 ,5 }); } public int changeTest (int amount, int [] coins) { int []dp=new int [amount+1 ]; dp[0 ]=1 ; for (int i = 1 ; i <= amount; i++) { for (int j = 0 ; j < coins.length; j++) { if (i>=coins[j]){ dp[i]+=dp[i-coins[j]]; } } } print(dp); Arrays.fill(dp,0 ); dp[0 ]=1 ; for (int i = 0 ; i < coins.length; i++) { for (int j = coins[i]; j <=amount; j++) { dp[j]+=dp[j-coins[i]]; } } print(dp); return dp[amount]; } public void print (int []dp) { for (int i : dp) { System.out.print(i+" " ); } System.out.println(); }
代码打印的结果是:
1 1 2 3 5 9
1 1 2 2 3 4
难度中等1496
给你一个整数 n
,返回 和为 n
的完全平方数的最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1
、4
、9
和 16
都是完全平方数,而 3
和 11
不是。
数学解法
首先要知道一个数学定理
任何一个正整数都可以表示成不超过四个整数的平方之和。 推论:满足四数平方和定理的数n(四个整数的情况),必定满足 n=4^a(8b+7)
那么我们同个这个定理来实现代码即可
推论 : 满足四数平方和定理的数n(四个整数的情况),必定满足 n=4^a(8b+7)
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public int numSquares (int n) { while (n % 4 == 0 ) n /= 4 ; if (n % 8 == 7 ) return 4 ; for (int i = 0 ; i*i <= n; i++) { if (i*i==n){ return 1 ; } } for (int i = 0 ; i*i <= n; i++) { for (int j = i; j*j <= n; j++) { if (j*j+i*i==n){ return 2 ; } } } return 3 ; } }
难度中等693
给你一个由 不同 整数组成的数组 nums
,和一个目标整数 target
。请你从 nums
中找出并返回总和为 target
的元素组合的个数。
题目数据保证答案符合 32 位整数范围。
看到组合就想到回溯 , 但是本题元素不可重复, 如果nums[i]
, 较小,target
较大,就会导致回溯的次数极多 ,
举个例子, num[0]=1
, target=1000
, 仅仅是第一个元素我们就需要回溯1000
次
回溯代码
使用函数传递参数比直接使用全局变量速度要快, 但是算法时间优化的问题不足以被弥补
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { LinkedList<Integer> track=new LinkedList <>(); int res=0 ; int []nums; public int combinationSum4 (int [] nums, int target) { this .nums=nums; backTrack(0 ,target); return res; } void backTrack (int start,int target) { if (target==0 ){ res++; return ; } for (int i = start; i <nums.length ; i++) { if (target<nums[i]) continue ; track.add(nums[i]); backTrack(start,target-nums[i]); track.removeLast(); } } }
动态规划
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public int combinationSum4 (int [] nums, int target) { int [] dp = new int [target + 1 ]; dp[0 ] = 1 ; for (int i = 1 ; i <= target; i++) { for (int num : nums) { if (num <= i) { dp[i] += dp[i - num]; } } } return dp[target]; } }
难度中等5244
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请
你返回所有和为 0
且不重复的三元组。
**注意:**答案中不可以包含重复的三元组。
双指针
思路 :
对数组进行排序
先设定一个前置的值
然后定义两个指针, 指向当前的值得后面的元素 l=i+1 , r=len-1 , 这里类似于二分的思想
随后遍历数组 , 移动指针, 根据情况来调整 l r 的位置,
注意对结果的去重
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class Solution { public List<List<Integer>> threeSum (int [] nums) { if (nums==null ||nums.length<3 ) return null ; List<List<Integer>> res=new ArrayList <>(); Arrays.sort(nums); for (int i = 0 ; i < nums.length; i++) { if (nums[i]>0 ){ return res; } if (i>0 &&nums[i]==nums[i-1 ]) continue ; int left=i+1 , right=nums.length-1 ; while (left<right){ int sum=nums[i]+nums[left]+nums[right]; if (sum==0 ){ List<Integer> tmp=new ArrayList <>(); tmp.add(nums[i]); tmp.add(nums[left]); tmp.add(nums[right]); res.add(tmp); while (left<right&&nums[left]==nums[left+1 ]) left++; while (right>left&&nums[right]==nums[right-1 ]) right--; left++; right--; }else if (sum<0 ){ left++; }else if (sum>0 ){ right--; } } } return res; } }
以数组 intervals
表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi]
。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
**思路 **
一个区间可以表示为 [start, end]
,先按区间的 start
排序:
我们首先按照左临界点 , 对区间进行排序 ,
对于每一个临界点, 我们遍历他之后的区间, 并且在这个过程中不断地扩充右临界点 r=Math.max(intervals[j][1],r);
当我们找到了一个区间 : 该区间满足其左临界点 ==等于== 我们保存的右临界点 , 这个时候我们需要合并区间, 并且 直接跳到下一个 需要计算的区间
不需要再次计算当前的区间, 因为他的右临界点已经被包含了]\
如果我们找到区间 : 该区间满足其**左临界点 **==大于== 我们保存的右临界点 , 此时我们直接直接添加保存的左临界点以及右临界点, 并且以这个区间为基础再次开始计算即可
上述两种情况的区别体现在 前者为 i=j
, 后者为i=j-1
, 因为对于后者, 其左临界点不属于之前的区间, 因此需要再次对其计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int [][] merge(int [][] intervals) { LinkedList<int []> res=new LinkedList <>(); Arrays.sort(intervals,(a,b)->{ return a[0 ]-b[0 ]; }); res.add(intervals[0 ]); for (int i = 1 ; i < intervals.length; i++) { int []cur=intervals[i]; int []last=res.getLast(); if (cur[0 ]>last[1 ]){ res.add(cur); }else { last[1 ]=Math.max(cur[1 ],last[1 ]); } } return res.toArray(new int [0 ][0 ]); } }
难度简单1571
给定一个大小为 n
的数组 nums
,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋
的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
摩尔投票法
首先定义res
作为众数, vote
表示投票数
当遍历到num[i]
时
如果vote==0
, 那么我们就直接假设当前的这个num
就是众数 ,并且vote+1
如果vote!=0
, 那么就直接进行运算, 如果num
与当前假设的众数相同, 那么就vote+1
,反之 vote-1
因为题目给的众数的个数超过数组长度的一半, 所以可以假设数组中只有两种数值的数字
如果前面都是其他的数字, 那么 就相当于这些数字在内耗, 3,2,4,6,5,6,2,2,7,8,1,1,1,2,2,2,2,2,2
如果众数的分布比较集中, 那么就是数量碾压 (**数组中有一个数字出现的次数超过数组长度的一半 **)
代码如下
1 2 3 4 5 6 7 8 9 10 11 class Solution { public int majorityElement (int [] nums) { int res=nums[0 ],vote=0 ; for (int i = 1 ; i < nums.length; i++) { res=vote<0 ?nums[i]:res; vote+=nums[i]==res?1 :-1 ; } return res; } }
难度中等1402
给定一个包含红色、白色和蓝色、共 n
个元素的数组 nums
,**原地 **对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
我们使用整数 0
、 1
和 2
分别表示红色、白色和蓝色。
必须在不使用库的sort函数的情况下解决这个问题。
思路
统计三种颜色的个数, 然后覆盖原有的颜色即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public void sortColors (int [] nums) { int a=0 ,b=0 ,c=0 ; for (int i : nums) { if (i==0 )a++; else if (i==1 )b++; else if (i==2 )c++; } int idx=0 ; while (a--!=0 ){ nums[idx++]=0 ; } while (b--!=0 ){ nums[idx++]=1 ; } while (c--!=0 ){ nums[idx++]=2 ; } } }
快排模板
以及测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @Test public void qsTest () { int size=10 ; for (int i = 0 ; i < 50 ; i++) { int []arr=generateArray(size); int []nums=Arrays.copyOf(arr,arr.length); Arrays.sort(arr); quickSort(0 ,nums.length-1 ,nums); System.out.println(Arrays.equals(arr,nums)); } } public int [] generateArray(int size){ int []nums=new int [size]; for (int i = 0 ; i < size; i++) { nums[i]=(int )(Math.random()*1000 ); } return nums; } void quickSort (int i,int j,int []nums) { if (i>j) return ; int l=i,r=j; int pivot=nums[i]; while (l<r){ while (l<r&&nums[r]>=pivot){ r--; } while (l<r&&nums[l]<=pivot){ l++; } if (l<r){ int tmp=nums[l]; nums[l]=nums[r]; nums[r]=tmp; } } nums[i]=nums[l]; nums[l]=pivot; quickSort(i,l-1 ,nums); quickSort(l+1 ,j,nums); }
遗留问题
快排为啥要从右边开始遍历 ?
简单的举个例子即可
快排为什么必须先从右往左查找(排升序)_lucy-bit的博客-CSDN博客
1 2 3 4 5 6 while (l<r&&nums[l]<=base){ l++; } while (l<r&&nums[r]>=base){ r--; }
例如int[] arr = {6,1,2,7,9,8,4,5,10};
先以6 为基准数,
加入我们是 i 先遍历, i => 7
然后从右向左遍历 j=> 5
arr : {6,1,2,5,9,8,4,7,10}
i++ j--
此时i => 9 , j=> 4
那么 此时条件都满足 i<j
再次交换, 得到arr : {6,1,2,5,4,8,9,7,10}
j--
, 此时i, j
相遇, 退出循环
交换基准数 , 得到 arr : {8 1 2 5 4 6 9 7 10 }
此时我们可以发现, 6 的左边依然存在 大于6 的数字
而正确的结果应该是 4 1 2 5 6 8 9 7 10
难度中等1423
给定一个 n × n 的二维矩阵 matrix
表示一个图像。请你将图像顺时针旋转 90 度。
你必须在** 原地 ** 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
自外向内, 循环旋转矩阵
自外向内一共有不超过 n/2
层(单个中心元素不算一层)矩形框。对于第 times
层矩形框,其框边长len=nums−(times∗2)
,
将其顺时针分为 4 份 len-1
的边,对四条边进行元素的循环交换即可。
注意循环的边界问题即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public void rotate (int [][] matrix) { for (int i = 0 ; i < matrix.length/2 ; i++) { for (int j = 0 ; j < (matrix.length+1 )/2 ; j++) { int temp = matrix[i][j]; matrix[i][j]=matrix[matrix.length-j-1 ][i]; matrix[matrix.length-j-1 ][i]=matrix[matrix.length-i-1 ][matrix.length-j-1 ]; matrix[matrix.length-i-1 ][matrix.length-j-1 ]=matrix[j][matrix.length-i-1 ]; matrix[j][matrix.length-i-1 ]=temp; } } } }
难度中等1127
编写一个高效的算法来搜索 *m* x *n*
矩阵 matrix
中的一个目标值 target
。该矩阵具有以下特性:
每行的元素从左到右升序排列。
每列的元素从上到下升序排列。
倒序遍历矩阵, 利用题目给的矩阵是升序的特性, 免去不必要的计算,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { public boolean searchMatrix (int [][] matrix, int target) { for (int i = matrix.length - 1 ; i >= 0 ; i--) { if (matrix[i][0 ]>target||matrix[i][matrix[0 ].length-1 ]<target){ continue ; }else { int l=0 ,r=matrix[0 ].length; while (l<r){ int mid=(l+r)/2 ; int cur=matrix[i][mid]; if (target<cur){ r=mid; }else if (target>cur){ l=mid+1 ; }else { return true ; } } } } return false ; } }
难度中等715
编写一个高效的算法来判断 m x n
矩阵中,是否存在一个目标值。该矩阵具有如下特性:
每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。
思路与上一题相同
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public boolean searchMatrix (int [][] matrix, int target) { if (target>matrix[matrix.length-1 ][matrix[0 ].length-1 ]||target<matrix[0 ][0 ]) return false ; int line=-1 ; for (int i = 0 ; i < matrix.length; i++) { if (matrix[i][0 ]>target){ line=i; break ; }else if (matrix[i][0 ]==target) return true ; } int []nums; if (line==-1 ) nums=matrix[matrix.length-1 ]; else nums=line==0 ?matrix[0 ]:matrix[line-1 ]; return binarySearch(nums,target); } boolean binarySearch (int []nums,int target) { int l=0 ,r=nums.length; while (l<r){ int mid=l+ (r-l)/2 ; if (target>nums[mid]) l=mid+1 ; else if (target<nums[mid]) r=mid; else return true ; } return false ; } }
难度中等1212
给你一个 m
行 n
列的矩阵 matrix
,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
本题是顺时针遍历矩阵
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { public List<Integer> spiralOrder (int [][] matrix) { List<Integer> res = new ArrayList <>(); int l = 0 , r = matrix[0 ].length-1 , top = 0 , bot = matrix.length-1 ; while (true ) { for (int i = l; i <= r; i++) { res.add(matrix[top][i]); } if (++top > bot) break ; for (int i = top; i <= bot; i++) { res.add(matrix[i][r]); } if (--r < l) break ; for (int i = r; i >= l; i--) { res.add(matrix[bot][i]); } if (--bot < top) break ; for (int i = bot; i >= top; i--) { res.add(matrix[i][l]); } if (++l > r) break ; } return res; } }
难度中等822
给你一个正整数 n
,生成一个包含 1
到 n2
所有元素,且元素按顺时针顺序螺旋排列的 n x n
正方形矩阵 matrix
。
本题是构造矩阵
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public int [][] generateMatrix(int n) { int [][]matrix=new int [n][n]; int []nums=new int [n*n]; for (int i = 1 ; i <=n*n; i++) { nums[i-1 ]=i; } int top=0 ,bot=n-1 ,l=0 ,r=n-1 ,idx=0 ; while (true ){ for (int i = l; i <= r; i++) { matrix[top][i]=nums[idx++]; } if (++top>bot) break ; for (int i = top; i <= bot; i++) { matrix[i][r]=nums[idx++]; } if (--r<l) break ; for (int i = r; i >=l ; i--) { matrix[bot][i]=nums[idx++]; } if (--bot<top) break ; for (int i = bot; i >= top; i--) { matrix[i][l]=nums[idx++]; } if (++l>r) break ; } return matrix; } }
总结
以上两道题的关键点在与遍历的方式, 定义四个遍历
分别负责上下左右行列的遍历, 注意好边界问题即可
难度中等1267
给你一个整数数组 nums
,返回 数组 answer
,其中 answer[i]
等于 nums
中除 nums[i]
之外其余各元素的乘积 。
题目数据 保证 数组 nums
之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
请**不要使用除法,**且在 O(*n*)
时间复杂度内完成此题。
对于 下标为 i
的 数组中的元素numsp[i]
,
我们以i
为中心, 分别求出他左右两边的元素的累乘的结果
这种思路如果不使用任何技巧, 实现如下
1 2 3 4 5 6 7 8 9 10 int []res=new int [nums.length];for (int i = 0 ; i < nums.length; i++) { res[i]=nums[i]; for (int j = 0 ; j < i; j++) { res[i]*=nums[j]; } for (int k = i+1 ; k < nums.length; k++) { res[i]*=nums[k]; } }
时间复杂度为O(n2 ) ,
果不其然TLE
可以发现, 在这个过程中, 我们做了许多的重复计算
解决的方法是用数组提前存储前某项累乘的乘积
那么便有了下边的写法,
我们使用res[]'
先遍历 nums
, 提前在 nums[i]
对应的i
下标, 存储下前i
项的乘积(注意这里不能乘以nums[i]
),
然后我们倒序遍历, 从后面求右半部分的累乘的乘积
然后左右两边相乘, 即可得到除了当前位置元素的乘积
初始化 res
res[i]=res[i-1]*a[i-1];
后半部分累乘 倒数遍历
tmp*=a[i+1];
随后左右两边相乘, 赋值给res[i]
, 最后返回res
即可
完整代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int [] productExceptSelf(int []a){ int n = a.length; if (n==0 ) return new int [0 ]; int []res=new int [n]; res[0 ]=1 ; int tmp=1 ; for (int i = 1 ; i < res.length; i++) { res[i]=res[i-1 ]*a[i-1 ]; } for (int i = n - 2 ; i >= 0 ; i--) { tmp*=nums[i+1 ]; res[i]*=tmp; } return res; } }
难度中等804
给定一个区间的集合 intervals
,其中 intervals[i] = [starti, endi]
。返回 需要移除区间的最小数量,使剩余区间互不重叠 。
本题与56. 合并区间 类似, 都是区间类问题
难度中等612
给你一个整数数组 nums
,判断这个数组中是否存在长度为 3
的递增子序列。
如果存在这样的三元组下标 (i, j, k)
且满足 i < j < k
,使得 nums[i] < nums[j] < nums[k]
,返回 true
;否则,返回 false
。
看到本题首先想到了DP做法
做法与300. 最长递增子序列 : DP 类似
但是TLE 了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public boolean increasingTriplet (int [] nums) { int res=0 ; int []dp=new int [nums.length]; Arrays.fill(dp,1 ); dp[0 ]=1 ; for (int i = 0 ; i < nums.length; i++) { for (int j = 0 ; j <i; j++) { if (nums[i]>nums[j]){ dp[i]=Math.max(dp[i],dp[j]+1 ); } res=Math.max(res,dp[i]); } } return res>=3 ; } }
难度中等3
「力扣嘉年华」的中心广场放置了一个巨型的二叉树形状的装饰树。每个节点上均有一盏灯和三个开关。节点值为 0
表示灯处于「关闭」状态,节点值为 1
表示灯处于「开启」状态。每个节点上的三个开关各自功能如下:
开关 1
:切换当前节点的灯的状态;
开关 2
:切换 以当前节点为根 的子树中,所有节点上的灯的状态,;
开关 3
:切换 当前节点及其左右子节点 (若存在的话) 上的灯的状态;
给定该装饰的初始状态 root
,请返回最少需要操作多少次开关,可以关闭所有节点的灯。
输入:root = [1,1,0,null,null,null,1]
输出:2
解释:以下是最佳的方案之一,如图所示
树形DP
树形DP准确的说是一种DP的思想,将DP建立在树状结构的基础上。整体的思路大致就是用树形的结构存储数据。
方法:
vector dp(TreeNode *root)返回 以root为根的数的对应的四种情况需要的操作数
vec[0] 全关 vec[1]只有根开 vec[2] 除根开 vec[3]全开
方法执行步骤:
首先dp左右孩子, 获取以左右孩子为根节点对应的变为四种情况需要的最小的操作数
然后枚举以子节点为根的子树的状态
a 表示子节点的状态
b 表示除了子节点之外其他节点的状态(这里的其他节点指的是 子节点下面的节点(以子节点为根节点))
每种操作最多做一次,因此用二进制枚举做了哪些操作
x 表示 操作1 (改变当前节点) y表示操作2 (改变所有节点上的灯的状态) z表示操作3 (改变左右子树)
// i 000 001 010 011 100 101 110 111
// x 000 001 000 001 000 001 000 001
// y 000 000 001 001 000 000 001 001
// z 000 000 000 000 100 001 001 001
// 全关 x y x y z x z y z 全开
aa表示子节点
bb表示除了子节点的其他节点
cc 表示当前节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 class Solution { struct TreeNode { int val; TreeNode *left; TreeNode *right; TreeNode (int x) : val (x), left (NULL ), right (NULL ) {} }; const int INF = (int )1e9 ; vector<int > dp (TreeNode *node) { if (node == nullptr ) return {0 , 0 , 0 , 0 }; vector<int > L = dp (node->left), R = dp (node->right); vector<int > vec = {INF, INF, INF, INF}; for (int a = 0 ; a < 2 ; a++) for (int b = 0 ; b < 2 ; b++) { int from = b * 2 + a; int c = node->val; for (int i = 0 ; i < 8 ; i++) { int x = i & 1 , y = i >> 1 & 1 , z = i >> 2 & 1 ; int aa = (y ^ z ? 1 - a : a); int bb = (y ? 1 - b : b); int cc = (x ^ y ^ z ? 1 - c : c); if (aa != bb) continue ; vec[aa * 2 + cc] = min (vec[aa * 2 + cc], L[from] + R[from] + x + y + z); } } return vec; } public : int closeLampInTree (TreeNode *root) { return dp (root)[0 ]; } };
如果当前节点的灯是开的,我们可以:(1) 操作一次开关1;(2) 操作一次开关2;(3) 操作一次开关3;(4) 操作开关123。
即操作最小奇数次开关让当前节点的灯关掉 。
如果当前节点的灯是关的,我们需要保持当前的状态,我们可以:(1) 不动开关;(2) 操作一次开关1+操作一次开关2;(3) 操作一次开关1+操作一次开关3;(4) 操作一次开关2+操作一次开关3。
即操作最小偶数次开关,使得对于当前节点而言,你操作了等于没操作,当前节点的灯还是关的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 class Solution { int [][] sz = new int [100010 ][2 ]; int [][] dp = new int [100010 ][4 ]; int idx = 0 ; int dfs1 (TreeNode p) { if (p == null ) return 0 ; int rt = ++idx; sz[rt][p.val] = 1 ; int l = dfs1(p.left),r = dfs1(p.right); sz[rt][0 ] += sz[l][0 ] + sz[r][0 ]; sz[rt][1 ] += sz[l][1 ] + sz[r][1 ]; dp[rt][0 ] = sz[rt][0 ]; dp[rt][3 ] = sz[rt][1 ]; dp[rt][1 ] = sz[l][0 ] + sz[r][0 ] + p.val; dp[rt][2 ] = sz[l][1 ] + sz[r][1 ] + 1 - p.val; if (l == 0 &&r == 0 ) return rt; int x = p.val; int y = 1 - p.val; dp[rt][1 ] = Math.min(dp[rt][1 ],dp[l][0 ] + dp[r][0 ] + x); dp[rt][1 ] = Math.min(dp[rt][1 ],dp[l][3 ] + dp[r][3 ] + 1 + y); dp[rt][1 ] = Math.min(dp[rt][1 ],dp[l][1 ] + dp[r][1 ] + 1 + y); dp[rt][2 ] = Math.min(dp[rt][2 ],dp[rt][1 ] + 1 ); dp[rt][2 ] = Math.min(dp[rt][2 ],dp[l][3 ] + dp[r][3 ] + y); dp[rt][2 ] = Math.min(dp[rt][2 ],dp[l][0 ] + dp[r][0 ] + 1 + x); dp[rt][2 ] = Math.min(dp[rt][2 ],dp[l][2 ] + dp[r][2 ] + 1 + x); dp[rt][0 ] = Math.min(dp[rt][0 ],dp[rt][1 ] + 1 ); dp[rt][0 ] = Math.min(dp[rt][0 ],dp[l][0 ] + dp[r][0 ] + y); dp[rt][0 ] = Math.min(dp[rt][0 ],dp[l][3 ] + dp[r][3 ] + 1 + x); dp[rt][0 ] = Math.min(dp[rt][0 ],dp[l][1 ] + dp[r][1 ] + 1 + x); dp[rt][3 ] = Math.min(dp[rt][3 ],dp[rt][0 ] + 1 ); dp[rt][3 ] = Math.min(dp[rt][3 ],dp[l][3 ] + dp[r][3 ] + x); dp[rt][3 ] = Math.min(dp[rt][3 ],dp[l][0 ] + dp[r][0 ] + 1 + y); dp[rt][3 ] = Math.min(dp[rt][3 ],dp[l][2 ] + dp[r][2 ] + 1 + y); dp[rt][0 ] = Math.min(dp[rt][0 ],dp[rt][3 ] + 1 ); dp[rt][1 ] = Math.min(dp[rt][1 ],dp[rt][2 ] + 1 ); return rt; } public int closeLampInTree (TreeNode root) { dfs1(root); return dp[1 ][3 ]; } }