文章目录
前言
需要开通vip的题目暂时跳过
笔记导航
点击链接可跳转到所有刷题笔记的导航链接
321. 拼接最大数
给定长度分别为 m 和 n 的两个数组,其元素由 0-9 构成,表示两个自然数各位上的数字。现在从这两个数组中选出 k (k <= m + n) 个数字拼接成一个新的数,要求从同一个数组中取出的数字保持其在原数组中的相对顺序。
求满足该条件的最大数。结果返回一个表示该最大数的长度为 k 的数组。
说明: 请尽可能地优化你算法的时间和空间复杂度。
- 解答
public int[] maxNumber(int[] nums1, int[] nums2, int k) {
int[] res = new int[k];
for (int i = 0; i <= k; i++) {
int j = k - i;
if (i > nums1.length || j > nums2.length) continue;
int[] max1 = findMax(nums1, nums1.length - i);
int[] max2 = findMax(nums2, nums2.length - j);
int[] temp = merge(max1, max2);
res = compare(res, 0, temp, 0) ? res : temp;
}
return res;
}
public int[] findMax(int[] nums, int k) {//删除k个使得数字最大
if (k == nums.length) return new int[0];
int number = 0;
Stack<Integer> stack = new Stack<>();
stack.add(nums[0]);
for (int i = 1; i < nums.length; i++) {
while (!stack.isEmpty() && number < k) {
int top = stack.peek();
if (nums[i] > top) {
stack.pop();
number++;
} else break;
}
stack.add(nums[i]);
}
while(number < k){
stack.pop();
number++;
}
int[] res = new int[stack.size()];
int index = res.length - 1;
while (!stack.isEmpty())
res[index--] = stack.pop();
return res;
}
public int[] merge(int[] nums1, int[] nums2) {//合并
int[] res = new int[nums1.length + nums2.length];
int index1 = 0;
int index2 = 0;
int index = 0;
while ((index1 < nums1.length || index2 < nums2.length) && index < res.length) {
if (index1 == nums1.length) {
while (index2 < nums2.length && index < res.length) res[index++] = nums2[index2++];
} else if (index2 == nums2.length) {
while (index1 < nums1.length && index < res.length) res[index++] = nums1[index1++];
} else {
if (nums1[index1] < nums2[index2]) res[index++] = nums2[index2++];
else if (nums1[index1] > nums2[index2]) res[index++] = nums1[index1++];
else {
if (compare(nums1, index1, nums2, index2)) res[index++] = nums1[index1++];
else res[index++] = nums2[index2++];
}
}
}
return res;
}
public boolean compare(int[] nums1, int index1, int[] nums2, int index2) {//比较
while (index1 < nums1.length && index2 < nums2.length) {
if (nums1[index1] > nums2[index2]) return true;
else if (nums1[index1] < nums2[index2]) return false;
index1++;
index2++;
}
if (index1 < nums1.length) return true;
else return false;
}
-
分析
- 两个数组选出k个构成最大数字。并且选择的同一数组中的数字保持有序。可以将问题拆分成从数组1中选择一个m个数字组成的最大数字和数字2中选择一个n个数字组成的最大数字。确保m+n = k。分治的思想。拆分成子问题。
- 子问题就是如何从一个数组中得到m个数字组成的最大值。
- 选出m个数字 实际就是删除数组长度-m个数字。记为n。
- 子问题就变成了给定数组中删除n个数字 使得剩余的数字构成的数最大。
- 第一个数字入栈。从第二个位置开始遍历数组。每次判断栈顶数字是否小于当前遍历的数字。如果小于,则将栈顶出栈。表示删除这个数字,记录删除个数。因为先入栈的是高位。数字高位越大表示数字也越大。所以小的删掉。当删除个数达到n的时候其余的数字不再删除。还有一种情况就是 遍历完了数组。当前删除的个数还不到n个。则从栈顶再删去,直到删掉n个数字。此时在栈中的数字所组成的就是删除n个数字后可以得到的最大值。
- 好了 子问题求解完毕了。得到了两个数组中分别的最大值。如何将这两者合并得到最大值是接下来要解决的问题。
- 同时遍历两个数组,选择数字更大的作为当前位。若数字一样,则比较后续的数字组合哪个更大。选择大的作为当前位。这样就可以得到两个数组组合成一个最大的值。
- 求解完所有的子问题 就可以得到原问题的答案。
-
提交结果
322. 零钱兑换
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
-
解答
int res3 = Integer.MAX_VALUE; public int coinChange(int[] coins, int amount) { Arrays.sort(coins); dfs(coins, amount, 0, coins.length - 1); return res3 == Integer.MAX_VALUE ? -1 : res3; } public void dfs(int[] coins, int amount, int number, int index) { if (amount == 0) { res3 = Math.min(res3, number); return; } if (index < 0) return; int n = amount / coins[index]; for (int i = n; i >= 0 && number + i < res3; i--) { dfs(coins, amount - coins[index] * i, number + i, index - 1); } }
-
分析
- 一开始按部就班的一个一个的硬币取的dfs会超时
- 所以索性一次性取n个硬币。n等于当前的金额除以当前硬币的面值。就是指这个硬币的个数。
- 金额减去拿的硬币的总价值,计数number加上拿的硬币数。index表示选择的硬币面值,进入下一层递归index-1。
- 一开始硬币先排好序。先从面值高的开始拿。
- 当金额等于0的时候。 计算当前硬币的数量,和已记录最小值比较 保留更小者。
- 若剩余的金额无法通过拿硬币归0,那么就回溯。拿n-1个硬币。
- 剪枝。当前拿的硬币数量大于已记录的硬币数量 则不再递归。
-
提交结果
324.摆动排序 II
给定一个无序的数组 nums,将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]… 的顺序。
- 解答
int n=-1;
public void wiggleSort(int[] nums) {
//找到中位数索引
int midIndex = this.quickSelect(nums,0,nums.length-1);
//找到中位数
int mid = nums[midIndex];
n=nums.length;
//三分法
for(int i=0,j=0,k=nums.length-1;j<=k;){
if(nums[V(j)]>mid){
swap(nums,V(j++),V(i++));
}else if(nums[V(j)]<mid){
swap(nums,V(j),V(k--));
}else{
j++;
}
}
}
public int V(int i){
return (1+2*(i)) % (n|1);
}
public void swap(int[] nums,int i,int j){
int t = nums[i];
nums[i]=nums[j];
nums[j]=t;
}
public int quickSelect(int[] nums,int left,int right){
int pivot = nums[left];
int l = left;
int r = right;
while(l<r){
while(l<r&&nums[r]>=pivot){
r--;
}
if(l<r){
nums[l++]=nums[r];
}
while(l<r&&nums[l]<=pivot){
l++;
}
if(l<r){
nums[r--]=nums[l];
}
}
nums[l]=pivot;
if(l==nums.length/2){
return l;
}else if(l>nums.length/2){
return this.quickSelect(nums,left,l-1);
}else{
return this.quickSelect(nums,l+1,right);
}
}
-
分析
- 一大一小数字穿插
- 所以可以使用中位数,将数组分成两堆,然后将两堆数字进行穿插的排列。
- 寻找中位数使用快速选择排序。相比于快速排序要递归两侧,快速选择排序只需要递归一侧即可。时间复杂度更低。
- 找到中位数后,左边的小于等于中位数,右边的大于等于中位数。
- 利用三分法进行数字的交换。
-
提交结果
326. 3的幂
给定一个整数,写一个函数来判断它是否是 3 的幂次方。
- 解答
//方法一
public boolean isPowerOfThree(int n) {
return Integer.toString(n, 3).matches("^10*$");
}
//方法二
public boolean isPowerOfThree(int n) {
return n > 0 && 1162261467 % n == 0;
}
-
分析
- 方法一 将n转换成3进制。当三进制数的首位为1 后面全是0的时候 就是3的幂
- n是int类型 所以最大的3的幂是1162261467,而3是质数,所以1162261467整除的数,就是3的幂
-
提交结果
方法一
方法二
327.区间和的个数
给定一个整数数组 nums,返回区间和在 [lower, upper] 之间的个数,包含 lower 和 upper。
区间和 S(i, j) 表示在 nums 中,位置从 i 到 j 的元素之和,包含 i 和 j (i ≤ j)。
-
解答
public int countRangeSum(int[] nums, int lower, int upper) { if(nums == null || nums.length == 0){ return 0; } //键值为区间和和这个区间和出现的次数 TreeMap<Long, Integer> tree = new TreeMap<>(); tree.put(0L, 1); int count = 0; long sum = 0L; for(int num : nums){ sum += num; //subMap()返回一个值在sum - upper 和sum - lower 之间的子集合,values()方法获得这个映射的值得视图 for(int cnt : tree.subMap(sum - upper, true, sum - lower, true).values()){ count += cnt; //统计满足条件的区间和个数 } tree.put(sum, tree.getOrDefault(sum, 0) + 1); } return count; }
-
分析
- 区间和的第一反应是使用前缀和来计算。sum[j] - sum[i] 来表示i-j的区间和
- 题目要求求区间和在给定范围内的个数 即满足lower <=sum[j] - sum[i]<=upper
- 变换下公式可得到 sum[j] - upper <= sum[i] <= sum[j] - lower
- 所以题目就变成了 给定sum[j] 、upper、lower 那么在上述范围内满足的sum[i]的个数有多少个
- 代码中的sum就等于这里的sum[j]
- TreeMap底层是红黑树。查找的时间复杂度为O(logN)。一边计算前缀和 一遍插入到树中。
- subMap()返回一个值在sum - upper 和sum - lower 之间的子集合 可以得到满足这个前缀和范围内的个数是多少。然后统计下来。
- tree.put(sum, tree.getOrDefault(sum, 0) + 1); 将前缀和和出现的次数+1
-
提交结果
328.奇偶链表
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
- 解答
public ListNode oddEvenList(ListNode head) {
if(head == null || head.next == null)return head;
ListNode odd = head;
ListNode even = head.next;
ListNode evenFirst = even;
while(even.next !=null){
odd.next = even.next;
odd = even.next;
if(odd.next!=null){
even.next = odd.next;
even = odd.next;
}else even.next = null;
}
odd.next = evenFirst;
return head;
}
-
分析
- 三个指针 一个是指向奇数结点 一个指向偶数结点 一个指向偶数结点的第一个。
- while循环,将奇数结点相连,偶数结点相连。最后奇数结点的最后一个的后继指向最开始记录的第一个偶数结点即可。
-
提交结果
329.矩阵中的最长递增路径
给定一个整数矩阵,找出最长递增路径的长度。
对于每个单元格,你可以往上,下,左,右四个方向移动。 你不能在对角线方向上移动或移动到边界外(即不允许环绕)。
- 解答
int[][] points = {{1,0},{-1,0},{0,1},{0,-1}};
public int longestIncreasingPath(int[][] matrix) {
if(matrix == null || matrix.length==0 || matrix[0].length==0)return 0;
int row = matrix.length;
int column = matrix[0].length;
int[][] dp = new int[row][column];
int max = 0;
for(int i = 0;i<row;i++){
for(int j = 0;j<column;j++){
max = Math.max(max,dfs(matrix,i,j,dp));
}
}
return max;
}
public int dfs(int[][] matrix,int row,int column,int[][] dp){
if(dp[row][column] != 0)
return dp[row][column];
++dp[row][column];
for(int[] point:points){
int newRow = row+point[0];
int newColumn = column+point[1];
if(newRow>=0 && newRow < matrix.length && newColumn >= 0 && newColumn < matrix[0].length && matrix[newRow][newColumn] > matrix[row][column])
dp[row][column] = Math.max(dp[row][column],dfs(matrix,newRow,newColumn,dp)+1);
}
return dp[row][column];
}
- 分析
- 纯dfs做的话会超时
- 所以这里就引入了dp数组来保留已经计算出的最长路径。避免重复计算。
- 提交结果
330. 按要求补齐数组
给定一个已排序的正整数数组 nums,和一个正整数 n 。从 [1, n] 区间内选取任意个数字补充到 nums 中,使得 [1, n] 区间内的任何数字都可以用 nums 中某几个数字的和来表示。请输出满足上述要求的最少需要补充的数字个数。
-
解答
public int minPatches(int[] nums, int n) { int res = 0, i = 0; long miss = 1; while (miss <= n) { if (i < nums.length && nums[i] <= miss) miss += nums[i++]; else { miss += miss; res++; } } return res; }
-
分析
- 举例说明吧,假设nums 表示[1,5,10] 一个指针i指向数组的第一个数字 此时是1.
- 缺少的数字从1开始判断。
- 若当前指针指向的数字小于等于缺少的数字。则将缺少的数字加上当前指针指向的数字 miss为2,指针后移一位。
- 继续判断。此时指针所指向的数字是5 miss为2 小于5,那么就说明找到了一个缺少的数字。res+1。 并且可以发现miss之前的数字都是可以组成的,所以2*miss-1 的数字是肯定可以得到的。此时miss修改为2倍的miss 为4
- 同理 4也小于5。 res+1, 1-4 同理 2* 4 -1的数字是肯定可以得到的。此时miss 修改为2倍的miss 为8.
- 8 > 5。 miss+5 = 13 大于10 结束循环。
- 可知找到了2个需要填充的数字。
-
提交结果