394.字符串解码
给定一个经过编码的字符串,返回它解码后的字符串。编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
输入:s = “3[a2[c]]”
输出:“accaccacc”
- 用栈保存’[‘前数字最高位的索引下标,每次搜索到’]'从栈中取出左端点,字符串删除并插入对应数量字符串。
- StringBuilder.delete(int start,int end)从原字符串删除下标为start到end-1的子串。
- StringBuilder.insert(int start,String s)在原字符串下标start位置插入字符串s(首字符下标为start)。
122.买卖股票的最佳时机Ⅱ
给定一个数组 prices ,其中 prices[i] 表示股票第 i 天的价格。在每一天,你可能会决定购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以购买它,然后在 同一天 出售。返回 你能获得的 最大 利润 。
输入: prices = [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
- 双指针找到每个波底和波峰,两者相减为一次最佳买卖,之后更新波底low=high+1,继续找下一对波峰波底。
283.移动零
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
- 双指针。左指针左边时非零数,右指针到左指针之间全零。每次右指针右移时找到非零数并放到左指针位置。
198.打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
输入:[2,7,9,3,1]
输出:12
- 动规。dp[i][0]表示第i间房子不偷窃所得最高金额,dp[i][1]表示偷窃第i间房子的最高金额。状态转移方程:dp[i][0]=Math.max(dp[i-1][1],dp[i-1][0]),dp[i][1]=Math.max(dp[i-2][1]+nums[i],dp[i-1][0]+nums[i])空间大小O(2n)。
- 可以只开辟O(n)空间,dp[i]表示考虑第i间房子所能获得的最高金额。则有
dp[i]=max(偷窃第i间房子,不偷窃第i间房子)=Math.max(dp[i-2]+nums[i],dp[i-1]),注意我们完全不需要关注dp[i]到底是偷了还是没偷,如果i-1间房子没偷,dp[i-2]等于dp[i-1],结果是一样的。 - 更进一步优化,只需要两个变量维护前两个房间最高金额+一个变量维护当前房间最高金额。空间O(1)
498.对角线遍历
给你一个大小为 m x n
的矩阵 mat
,请以对角线遍历的顺序,用一个数组返回这个矩阵中的所有元素。
输入:mat = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,4,7,5,3,6,8,9]
- 遍历方向分为两种,右上方遍历和左下方遍历。向上遍历时,碰到上边界时横坐标加一,碰到右边界时纵坐标加一;向下遍历碰到左边界纵坐标加一,碰到下边界横坐标加一。
- 保存结果时如果先用容器保存,则ArrayList.toArray()会直接返回Object[],强转类型会报异常,如果采用带形参方法,ArrayList.toArray(new E[])形参数组只能是对象数组,而不能是基本类型数组,因此要从ArrayList转换成int[]只能通过get方法一个个取出来。
912. 归并排序
给出一个无序数组,将该数组升序排列。
- 归并排序。将两个有序数组合并成一个有序数组,方法有两种,如果开辟一个O(M+N)空间的数组来保存临时有序数组,时间是O(n);或者用直接插入排序,时间O( n 2 n^2 n2)。这里给出的是直接插入的方法:
public int[] sortArray(int[] nums) {
mergeSort(nums,0,nums.length-1);
return nums;
}
public void mergeSort(int[] nums,int low,int high)
{
if(low==high) return ;
int mid=(low+high)/2;
mergeSort(nums,low,mid);
mergeSort(nums,mid+1,high);
int i=mid+1,j=low;
while(i<=high)
{
while(j<i&&nums[i]>=nums[j])j++;
if(j==i) break;
int temp=nums[i];
for(int k=i-1;k>=j;k--)
nums[k+1]=nums[k];
nums[j]=temp;
i++;
}
return ;
}
662.二叉树最大宽度
给定一个二叉树,编写一个函数来获取这个树的最大宽度。树的宽度是所有层中的最大宽度。这个二叉树与满二叉树(full binary tree)结构相同,但一些节点为空。每一层的宽度被定义为两个端点(该层最左和最右的非空节点,两端点间的null节点也计入长度)之间的长度。
输入:
1 / \ 3 2 / \ \ 5 3 9
输出: 4
解释: 最大值出现在树的第 3 层,宽度为 4 (5,3,null,9)。
- 遍历一趟二叉树,把每个节点的线性表存储节点序号保存在节点val值上(合理利用资源),用一个双端队列Deque实现层次遍历,记录每层第一个节点和最后一个节点的序号差,相减即为该层的宽度。
152.乘积最大子数组
给你一个整数数组 nums
,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
输入: [2,3,-2,4]
输出: 6
-
一开始想法是压缩数组,因为题目保证数组元素都是整数,因此可以把连续的正整数子数组压缩为它们的乘积,之后再用双指针法遍历压缩后的数组,但是求最大乘积时情况比较复杂,要判断两个指针之间有一个负数?负数之间是否插有整数?碰到0又该怎么处理?可以达到时间O(n)空间O(1)。
-
动规。起初想用动规的时候发现,如果定义dp[i]表示插入nums[i]形成的最大子数组乘积,状态转移方程:
dp[i]=max(nums[i],dp[i-1]*nums[i]),最大乘积子数组会从负号断开,错过两个负号构成的最大子数组乘积的情况,就没使用动规。实际上,可以定义一个dpmax[i]表示插入nums[i]形成的最大子数组乘积,再定义dpmin[i]表示插入nums[i]形成的最小子数组乘积,这样子dpmax[i]就会有三种情况,如果nums[i]为正数,要么最大子数组没有断开dpmax[i]=dpmax[i-1]*nums[i],要么前面为负数,nums[i]另起一个最大子数组作为最左端dpmax[i]=nums[i]。而如果nums[i]为负数,它可能作为前面连续子数组中第二个负数dpmax[i]=dpmin[i-1]*nums[i]。因此状态转移方程有:dpmax[i]=Math.max(nums[i],dpmax[i-1]*nums[i],dpmin[i-1]*nums[i]);
dpmin[i]=Math.min(nums[i],dpmax[i-1]*nums[i],dpmin[i-1]*nums[i]);
59.螺旋矩阵Ⅱ
给你一个正整数 n
,生成一个包含 1
到 n2
所有元素,且元素按顺时针顺序螺旋排列的 n x n
正方形矩阵 matrix
。
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]
- 首先定义四个变量表示上下左右四个边界,矩阵上界为low,下界为high。生成时有两种思路,一种是从外层生成到内层,每次生成一层;另一种是每次在一个方向上生成,到头后开始下一个方向。按照一层层来生成的话,往右方向生成碰到右边界后,下边界加一,并且把当前坐标置为往下遍历生成的第一个坐标(i坐标加一,j坐标减一),其他几个方向同理。注意能否进入右方向生成的判断条件:
- 除了j坐标没有碰到右边界外,还需要用已生成元素数量来约束(只要没生成满,那么右和下方向只要对应坐标没触碰到边界,坐标肯定是合法的;否则仅限制一个坐标,另一个方向的坐标可能已经越界导致生成地址非法),if(ele<n*n+1&&j<right)。
- 也可以直接限制当前两个坐标是否合法来判断能否继续生成,if(i>low&&i<high&&j>left&&j<right)。
今日总结
刷题+算命
目前力扣刷了有一百来道题目打算放一放,下一阶段完成开题报告+复习整理面试知识点。