本文中的部分图片摘自相关题解榜主,如有侵权,请联系删除。
特别感谢k神在剑指Offer刷题路上提供的清晰图解、和堪称完美的思路与方法
小文目录:
- T1:两数之和
- T2 :两数相加
- T3 / O48:无重复字符的最长子串
- T4: 寻找两个正序数组的中位数
- T5 : 最长回文子串
- T6 : Z字形变换
- T7 : 整数反转
- T8: 字符串转换成整数
- T9: 回文数
- T10 / O19:正则表达式匹配
- T11:盛最多水的容器
- T12: 整数转罗马数字
- T14:最长公共前缀
- T15:三数之和
- T17: 电话号码的字母组合
- T19:删除链表的倒数第k节点
- T20:有效的括号
- T21 / O25:合并两个有序链表
- ?T22: 括号生成
- T23: 合并K个升序链表
- T26、80题目:删除有序数组重复项的通解
- T27: 删除元素
- T31:下一个排列
- T32:最长有效括号
- T33:搜索旋转排序数组
- T34:在排序数组中查找元素的第一个和最后一个位置
- T39: 组合总和
- T40:组合总和-||(不重复)【接上题】
- T42:接雨水
- T43:字符串相乘
- T46:全排列(不重复)【回溯★、DFS★】
- T47:全排列(重复)【接上题】
- T48: 旋转图像
- T49:字母异位词分组
- T53:最大子序和
- T54: 螺旋矩阵
- T55:跳跃游戏
- T56: 合并区间
- T62: 不同路径
- T64:最小路径和
- T70:爬楼梯
- T72:编辑距离
- T75:颜色分类
- T76: 最小覆盖子串
- T77:组合
- T78: 子集(无重复元素)
- T79:单词搜索
- T84:柱状图中最大的矩形
- T85: 最大矩形
- T90:子集-|| (含重复元素)
- T94 \ T145 \T144: 二叉树的中序遍历(+后序+前序)
- T96:不同的二叉搜索树
- T98:验证二叉搜索树
- T101: 对称二叉树
- T102:二叉树的层序遍历
- T104: 二叉树的最大深度
- T105: 从前序中序遍历构造二叉树
- T106: 从中序和后序遍历构造二叉树
- T114:二叉树展开为链表
- T121: 买卖股票的最佳时机
- T124:二叉树中最大路径和
- T125:验证回文串
- T128: 最长子序列
- T130:被围绕的区域
- T136:只出现1次的数字
- T139:单词拆分
- T141:环形链表
- T142:环形链表||
- T146: LRU缓存机制
- T148:排序链表
- T152: 乘积最大子数组
- T152: 最小栈
- T155题目: 最小栈
- T160: 相交链表
- T164: MaxGap 最大间距
- T169:多数元素
- T179:最大数(剑指46)
- T198:打家劫舍|
- T213:打家劫舍||
- T337: 打家劫舍 III
- T200:岛屿数量
- T206:反转链表
- T207: 课程表
- T208:实现Trie(前缀树)
- T210:课程表||
- T215: 数组中的第K个最大元素
- T221: 最大正方形
- T226:翻转二叉树
- T232: 用栈实现队列
- T234:回文链表
- T236: 二叉树的最近公共祖先
- T238: 除自身以外数组的乘积
- T239:滑动窗口最大值
- T240题目:搜索二维矩阵||
- T252: 用队列实现栈
- T264:丑数||
- T279:完全平方数
- T283:移动零
- T287: 寻找重复数
- T297:二叉树的序列化与反序列化
- T300: 最长上升子序列
- T301:删除无效的括号
- T309:最佳买卖股票时机含冷冻期
- T123:买卖股票的最佳时机|||
- T312: 戳气球
- T322: 零钱兑换
- T338:比特位计数
- T347: 前k个高频元素
- T394: 字符串解码
- ?T399:除法求值
- T402: 根据身高重建队列
- T416: 分割等和子集
- T435: 无重叠区间
- T437: 路径总和 III
- T438:找到字符串中所有字母异位词
- T448:找到所有数组中消失的数字
- T461:汉明距离
- T494: 目标和
- T498题目:对角线遍历(之字形打印数组)
- T538:把二叉搜索树转换为累加数
- T543: 二叉树的直径
- T560:和为K的子数组
- T581:最短无序连续子数组
- T617: 合并二叉树
- T621:任务调度器
- T647: 回文子串
- T739:每日温度(单调栈)
- T783:二叉搜索树节点最小距离
- T876: 找链表中间节点
- T1094:拼车
- T1248:统计优美子数组(前缀和)
- T1893: 检查是否区域内所有整数都被覆盖
LeetCode刷题 码 码 不停蹄
T1:两数之和

T2 :两数相加
题目:

思路:


代码:
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode node = new ListNode(-1);
ListNode cur = node;
int carry =0;//存储当前的进位
while(l1 != null || l2 != null){//只有l1、l2都为null时 退出循环
int x = l1 == null ? 0 : l1.val;//l1遍历完,l2没完,l1后面的补0
int y = l2 == null ? 0 : l2.val;//l2遍历完,l1没完,l2后面的补0
int sum = x+y+carry;//计算当前为上的l1、l2、和有无进位
carry = sum>9 ? 1 : 0;//或carry = sum/10;
sum = sum%10;//实际存入当前节点的值;
cur.next = new ListNode(sum);
cur = cur.next;
//后移l1、l2
if(l1 != null) l1 = l1.next;
if(l2 != null) l2 = l2.next;
}
if(carry == 1) cur.next = new ListNode(1);//当最后有进位时,新增最后一个节点并存入
return node.next;
}
}
T3 / O48:无重复字符的最长子串
传送门:Offer48
思路1:滑动窗口+哈希表

代码:
class Solution {
public int lengthOfLongestSubstring(String s) {
int l = s.length();
int res =0;
Map<Character,Integer> map = new HashMap<>();
for(int end =0,start = 0;end<l;end++){
char c = s.charAt(end);
if(map.containsKey(c)) start = Math.max(start,map.get(c)+1);
//map.get(c)+1:∵当前c重复,区间应该从它后面的位置开始
//为什么用Math.max : eg.[absgfb],当遇到后面的b时,此时应该从后面的b开始,
//如果没有max(start,..),而是start= map.get(c)+1 会从前面的b开始
res = Math.max(res,end-start+1);
map.put(c,end);
}
return res;
}
}
思路2:动态规划+哈希表
见传送门
T4: 寻找两个正序数组的中位数
题目:时间复杂度为 O(log (m+n))

思路:看到有序数组想到二分法



代码:
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int n = nums1.length;
int m = nums2.length;
//因为数组是从索引0开始的,因此我们在这里必须+1,即索引(k+1)的数,才是第k个数。
int left = (n + m + 1) / 2;
int right = (n + m + 2) / 2;
//将偶数和奇数的情况合并,如果是奇数,会求两次同样的 k
return (getKth(nums1, 0, n - 1, nums2, 0, m - 1, left) + getKth(nums1, 0, n - 1, nums2, 0, m - 1, right)) * 0.5;
}
private int getKth(int[] nums1, int start1, int end1, int[] nums2, int start2, int end2, int k) {
//因为索引和算数不同6-0=6,但是是有7个数的,因为end初始就是数组长度-1构成的。
//最后len代表当前数组(也可能是经过递归排除后的数组),符合当前条件的元素的个数
int len1 = end1 - start1 + 1;
int len2 = end2 - start2 + 1;
//让 len1 的长度小于 len2,这样就能保证如果有数组空了,一定是 len1
//就是如果len1长度大于len2,把getKth()中参数互换位置,即原来的len2就变成了len1,即len1,永远比len2小
if (len1 > len2) return getKth(nums2, start2, end2, nums1, start1, end1, k);//互换
//如果一个数组中没有了元素,那么即从剩余数组nums2的其实start2开始加k再-1.
//因为k代表个数,而不是索引,那么从nums2后再找k个数,那个就是start2 + k-1索引处就行了。因为还包含nums2[start2]也是一个数。因为它在上次迭代时并没有被排除
if (len1 == 0) return nums2[start2 + k - 1];
//如果k=1,表明最接近中位数了,即两个数组中start索引处,谁的值小,中位数就是谁(start索引之前表示经过迭代已经被排出的不合格的元素,即数组没被抛弃的逻辑上的范围是nums[start]--->nums[end])。
if (k == 1) return Math.min(nums1[start1], nums2[start2]);//终止条件,即最后输出的数
//为了防止数组长度小于 k/2,每次比较都会从当前数组所生长度和k/2作比较,取其中的小的(如果取大的,数组就会越界)
//然后素组如果len1小于k / 2,则定位到其末尾即len1
int i = start1 + Math.min(len1, k / 2) - 1;
int j = start2 + Math.min(len2, k / 2) - 1;
//如果nums1[i] > nums2[j],表示nums2数组中包含j索引,之前的元素,逻辑上全部淘汰,即下次从J+1开始。
//而k则变为k - (j - start2 + 1),即减去逻辑上排出的元素的个数(要加1,因为索引相减,相对于实际排除的时要少一个的)
if (nums1[i] > nums2[j]) {
return getKth(nums1, start1, end1, nums2, j + 1, end2, k - (j - start2 + 1));
}
else {// <= 时:
return getKth(nums1, i + 1, end1, nums2, start2, end2, k - (i - start1 + 1));
}
}
T5 : 最长回文子串
题目:

思路:中心扩展法
假设字符串的每个字符为一种初始中心,然后分别向这个字符左右两侧扩展,即判断两边的字符是否相同。如果两边的字母相同,我们就可以继续扩展;如果两边的字母不同,我们就可以停止扩展,因为在这之后的子串都不能是回文串了。我们枚举所有的「回文中心」并尝试「扩展」,直到无法扩展为止,此时的回文串长度即为此「回文中心」下的最长回文串长度。我们对所有的长度求出最大值,即可得到最终的答案。
例如:str = acdbdaastr=acdbbdaa,当中心索引是3时,对应字符b,然后分别从其两边扩展,d==d、c!=a,结束,返回长度3

代码:
class Solution {
public String longestPalindrome(String s) {
if (s == null || s.length() < 1){
return "";
}
// 初始化最大回文子串的起点和终点
int start = 0;
int end = 0;
// 遍历每个位置,当做中心位
for (int i = 0; i < s.length(); i++) {
// 分别拿到奇数偶数的回文子串长度
int len_odd = expandCenter(s,i,i);//情况1、当子串中心是一个字符,eg.abcba 中心是c
int len_even = expandCenter(s,i,i + 1);//情2、当子串中心是两个字符eg.abccba中心是cc
// 对比最大的长度
int len = Math.max(len_odd,len_even);
// 计算对应最大回文子串的起点和终点
if (len > end - start){
//eg.len=12(偶数,说明中间两个字符作为中心),i=6,则start=i-5=1;end=i+6=12;
// 区间为[1,12],中心位置是i=6、7
//eg.len= 1(奇数,说明中间1个字符作为中心),i=6,则start=i-5=1;end=i+5=11;
// 区间为[1,11],中心位置是i=6
start = i - (len - 1)/2;
end = i + len/2;
}
}
// 注意:这里的end+1是因为 java自带的左闭右开的原因
return s.substring(start,end + 1);
}
private int expandCenter(String s,int left,int right){
// left = right 的时候,此时回文中心是一个字符,回文串的长度是奇数
// right = left + 1 的时候,此时回文中心是一个空隙,回文串的长度是偶数
// 跳出循环的时候恰好满足 s.charAt(left) != s.charAt(right)
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)){
left--;
right++;
}
//因为跳出循环时,s.charAt(left)!= s.charAt(right),即不包括此时的left和right,所以应该是[left+1,right-1],即(right-1)-(left+1)+1=right-left-1
//回文串的长度是right-left+1-2 = right - left - 1
return right - left - 1;
}
}
动态规划:
class Solution {
public String longestPalindrome(String s) {
if(s.length()<2) return s;
int n = s.length();
boolean[][] dp = new boolean[n][n];//dp[i][j]:字符串从i到j是否为回文
int max_len = 1;//最长回文长度至少是1,∵一个字符也是回文
int start=0;//最长回文起点
int end=0;//最长回文终点
for(int r=1;r<n;r++){
for(int l=0;l<r;l++){
if(s.charAt(l) == s.charAt(r) && (r-l<3 || dp[l+1][r-1])){
dp[l][r] = true;
if(r-l+1>max_len){
max_len = r-l+1;
start=l;
end = r;
}
}
}
}
return s.substring(start,end+1);
}
}
T6 : Z字形变换
题目:

思路:
如下图:当给定n=3时的字符串是s"LEETCOD’, 则定义一个列表res,列表的长度是n,然后按照z字形顺序,即行号先增大后减小的顺序,即res[0],res[1],res[2],res[1],res[0], res[1],res[2]…依次添加s的字符。然后返回res = res[0] + res[1] + res[2]
1、res[i] += c: 把每个字符 c 填入对应行 si ;
2、i += flag: 更新当前字符 c 对应的行索引;
3、flag = - flag: 在达到 Z 字形转折点时,执行反向。
先L填到res[0]、E填到res[1]、E填到res[2]、T填到res[1]、C填到res[0]、O填到res[1]、D填到res[2]。
举例:
代码:
class Solution {
public String convert(String s, int numRows) {
if(numRows<2) return s;
List<StringBuilder> rows = new ArrayList<>();//定义列表存储每一行的字符串
for(int i =0;i<numRows;i++) rows.add(new StringBuilder());
int i=0;
int flag = -1;
//因为i=0时,即一开始就要先增大,所以flag是+1,又因为后面i==0时flag会反转,故初始化为-1,后面反转变为+1;
for(char c : s.toCharArray()){
rows.get(i).append(c);//依次添加s的字符 到对应的行
if(i==0 || i == numRows-1) flag = -flag;
//当i到第一行或者最后一行时,反转flag,即改变顺序,从增大到减小到从减小到增大
i += flag;//i后移
}
StringBuilder res = new StringBuilder();
for(StringBuilder row : rows) res.append(row);//把每一行的字符串拼接到一块即是最终答案
return res.toString();
}
}
T7 : 整数反转
题目:

思路:主要考虑溢出问题


溢出:


代码:
class Solution {
public int reverse(int x) {
int res = 0;
while(x!=0){
int pop = x%10;//取余 拿个位数
//判断溢出
if(res > Integer.MAX_VALUE/10 || (res == Integer.MAX_VALUE/10 && pop >7)){
return 0;
}
if(res < Integer.MIN_VALUE/10 || (res == Integer.MIN_VALUE/10 && pop <-8)){
return 0;
}
res = res*10 + pop;
x /= 10;
}
return res;
}
}
T8: 字符串转换成整数
传送门:Offer67
思路:主要考虑溢出问题,同上题T7.
T9: 回文数


T10 / O19:正则表达式匹配
思路:动态规划
传送门:Offer19
T11:盛最多水的容器
题目:

思路:双指针

举例:
图中的res=min() 改为 res =max()

代码:
class Solution {
public int maxArea(int[] height) {
int i =0;
int j = height.length-1;
int res = 0;
while(i<j){
res = height[i] > height[j] ?
Math.max(res,(j-i)*height[j--]) : //height[j]高度低,下次向内移动j
Math.max(res,(j-i)*height[i++]) ; //height[i]高度低,下次向内移动i
}
return res;
}
}
T12: 整数转罗马数字
题目:

思路:
贪心法则:我们每次尽量使用最大的数来表示。 比如对于 1994 这个数,如果我们每次尽量用最大的数来表示,依次选 1000,900,90,4,会得到正确结果 MCMXCIV。
先从数字左边开始 尽量用大的数表示当前位,主要考虑特殊情况,即非正常的情况(IV、IX、XL、…)
其余正常的情况则正常顺序添加多个即可,例如,3—> III,添加3次I;8—> 先添加V,再添I,I,I;
代码:
class Solution {
int[] value = new int[]{1000,900,500,400,100,90,50,40,10,9,5,4,1};//要从大到小放置
String[] symbl = new String[]{"M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"};
public String intToRoman(int num) {
StringBuilder res = new StringBuilder();
for(int i =0;i<value.length;i++){
while(num>=value[i]){
res.append(symbl[i]);
num -= value[i];
}
}
return res.toString();
}
}
T14:最长公共前缀
题目:
思路:
纵向扫描:从前往后遍历所有字符串的每一列,比较相同列上的字符是否相同,如果相同则继续对下一列进行比较,如果不相同则当前列不再属于公共前缀,当前列之前的部分为最长公共前缀。
代码:
class Solution {
public String longestCommonPrefix(String[] strs) {
if(strs == null || strs.length==0 ) return " ";
int len = strs[0].length();//选第1个字符串,计算其长度
int count = strs.length;//strs 有几个字符串
for(int i=0;i<len;i++){
char cur = strs[0].charAt(i);
for(int k=1;k<count;k++){//判断其他字符串在i位上的字符是否和cur相同
if(i==strs[k].length() || strs[k].charAt(i) != cur){
//如果不相同或者这个字符串已经遍历完
return strs[0].substring(0,i);
}
}
}
return strs[0];//否则说明strs[0]最短,是最长公串
}
}
T15:三数之和
题目:

思路:排序+双指针


代码:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
for(int k=0;k<nums.length-2;k++){//因为k后面有两个指针,到数组结尾时,k在倒数第三个位置,所以nums.length-2
if(nums[k] >0) break;
if(k>0 && nums[k] == nums[k-1]) continue;//去掉重复数字
int i = k+1;
int j = nums.length-1;
while(i<j){
int sum = nums[i] + nums[k] +nums[j];
if(sum <0){
while(i<j && nums[i] == nums[++i]) ;//i后移,并跳过所有的重复值
}else if(sum >0){
while(i<j && nums[j] == nums[--j]) ;//j前移,并跳过所有的重复值
}else{
res.add(new ArrayList<Integer>(Arrays.asList(nums[i],nums[k],nums[j])));
while(i<j && nums[i] == nums[++i]);//i后移,并跳过所有的重复值
while(i<j && nums[j] == nums[--j]) ;//j前移,并跳过所有的重复值
}
}
}
return res;
}
}
T17: 电话号码的字母组合
题目:

思路:回溯
例如输入的字符串时“23”,先对第一个字符‘2’,向下搜索,有a、b、c、;a下搜索有d、e、f…
回溯算法用于寻找所有的可行解,如果发现一个解不可行,则会舍弃不可行的解。在这道题中,由于每个数字对应的每个字母都可能进入字母组合,因此不存在不可行的解,直接穷举所有的解即可。
【注意】使用StringBuilder传入的都是同一个对象,所以在递归完成之后必须撤回上一次的操作,需要删除上一次添加的字符。如果不删除,在for循环里,下一次运行时,数据就被污染了。(ad,ae)变成(ad,ade),所以需要把本次的d删除再进入下一次,变成ae

代码:
class Solution {
// 数字到号码的映射
private String[] map = {"abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
// 路径(情况)
private StringBuilder path = new StringBuilder();
//结果
private List<String> res = new ArrayList<>();
public List<String> letterCombinations(String digits) {
if(digits==null || digits.length()==0) return res;
dfs(digits,0);
return res;
}
//回溯函数
public void dfs(String digits, int index){
if(path.length() == digits.length()){
res.add(path.toString());//路径长度达到字符串的长度,说明一种情况(路径)已经形成,如“23”长度是2,当每次路径长度是2时说明完成此次搜索,如ab;若输入字符串是“789”,则每次搜到长度3,如abc、abd、...
return;
}
String symbls = map[digits.charAt(index)-'2'];//根据映射,2对应abc,abc的索引是map的0,故相差2,即减2
for(char symbl : symbls.toCharArray()){
path.append(symbl);//添加当前一个字符
dfs(digits,index+1);
path.deleteCharAt(path.length()-1);//删除当前索引字符 避免下次出错
}
}
}
T19:删除链表的倒数第k节点
题目:

思路:快慢指针

代码:
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode pre = new ListNode(0);
pre.next = head;
ListNode quick = pre;
ListNode slow = pre;
//快指针先走n步
while(n != 0){
quick = quick.next;
n--;
}
//快慢同时走,直到快指针到链表尾部
while(quick.next != null){
slow = slow.next;
quick = quick.next;
}
slow.next = slow.next.next;//slow指向倒数n+1个节点 ,删除的是slow.next节点,即倒数第n节点
return pre.next;
}
}
T20:有效的括号
题目:

思路:辅助栈

代码:
class Solution {
private HashMap<Character,Character> map = new HashMap<>(){
{ put('[',']'); put('{','}'); put('(',')');}
};
public boolean isValid(String s) {
int n = s.length();
if(n%2 ==1) return false;
if(s.length()>0 && !map.containsKey(s.charAt(0))) return false;
Deque<Character> queue = new LinkedList<>();
for(char cur : s.toCharArray()){
if(map.containsKey(cur)){//如果是左括号 入栈
queue.addFirst(cur);
}else{//右括号
if(!queue.isEmpty()){
if(map.get(queue.pollFirst()) != cur) return false;
//当map的Key不包含时,说明是右括号;那么删除此时的栈顶,看栈顶元素的左括号和此时的右括号cur是否匹配
}else return false;
}
}
return queue.isEmpty();
}
}
T21 / O25:合并两个有序链表
传送门:剑指Offer25:
题目:
方法1:迭代法
//迭代法
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode node = new ListNode(-1);//头节点
ListNode cur = node;//当前节点
if(l1 == null && l2 == null) return l1;
while(l1 != null && l2 != null){
if(l1.val < l2.val){
cur.next = l1;//先把l1及它后边的节点赋给cur.next
l1 = l1.next;//l1后移
}else{
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;//当前节点后移
}
cur.next = l1==null ? l2 : l1;
return node.next;//去除头节点
}
}
方法2:递归法

class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1 == null ) return l2;//返回l2表示当前 需要向后链接的节点是l2
if(l2 == null ) return l1;//返回l1表示当前 需要向后链接的节点是l1
if(l1.val < l2.val){
l1.next = mergeTwoLists(l1.next,l2);//l1.next 表示
return l1;//返回l1表示当前 需要向后链接的节点是l1
}else{
l2.next = mergeTwoLists(l1,l2.next);
return l2;//返回l2表示当前 需要向后链接的节点是l2
}
}
}
?T22: 括号生成
类似:T301:删除无效括号
题目:

思路:DFS

代码:
class Solution {
public List<String> generateParenthesis(int n) {
List<String> res = new ArrayList<>();
dfs(0,n*2,0,n,"",res);//n对括号,共2n个括号,左括:1、右括:-1;得分最大max=n,满足条件则得分=0
return res;
}
public void dfs(int index , int len ,int score, int max, String path, List res){
if(index == len){//当index到达n时,返回,回溯
if(score == 0){//=0,满足条件,加入结果集
res.add(path);
}
return;//回溯
}
if(score +1 <= max){//可以添加左括号
dfs(index+1,len,score+1,max,path+"(",res);
}
if(score-1>=0){//可以添加右括号
dfs(index+1,len,score-1,max,path+")",res);
}
}
}
T23: 合并K个升序链表
类似O25:合并两个排序的链表
题目:
思路:
1、首先合并两个链表:见传送门

2、分治合并

代码:
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
return merge(lists,0,lists.length-1);
}
public ListNode merge(ListNode[] ans , int L , int R){
if(L==R) return ans[L];
if(L>R) return null;
int mid = (L+R)>>1;
return mergeTwoLists(merge(ans,L,mid),merge(ans,mid+1,R));
}
public ListNode mergeTwoLists(ListNode a, ListNode b){
if(a== null || b == null) return a==null? b : a;
ListNode head = new ListNode(0);
ListNode cur = head;
while(a!=null && b!=null){
if(a.val<b.val){
cur.next = a;
a = a.next;
}else{
cur.next = b;
b = b.next;
}
cur = cur.next;
}
cur.next = a==null? b : a ;
return head.next;
}
}
T26、80题目:删除有序数组重复项的通解
- 其中的最多出现次数我们定义为K,K可以为任意数(前提合理),此题中K=2;
- 则fast、slow指针从2开始,且判断num[fast]!=num[slow-2];
- 若K是其他数,则只需让fast、slow指针从K开始,且判断num[fast]!=num[slow-K];
class Solution {
public int removeDuplicates(int[] nums) {
int n = nums.length;
int fast;//快指针,遍历每一个数,指向当前要取数的位置
int slow;//指向每次调整后的有效数组的末尾后一个,指向当前要放数的位置
// fast用来从后面的数组取数,然后放到slow指的位置;
//如果满足num[fast]!=num[slow-2],说明重复不超过2个,可以放,fast,slow后移;
//反之则说明num[fast]重复,不需要放,fast后移,slow不变
if(n<=2){
return n;
}
fast = 2;//从2开始,因为n<2 肯定不会存在重复超过2个
slow = 2;
while(fast<n){
if(nums[fast] != nums[slow-2]){
nums[slow] = nums[fast];
slow++;
}
fast++;
}
return slow;
}
}
T27: 删除元素
- 题目:
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。 - 代码:
class Solution {
public int removeElement(int[] nums, int val) {
int n = nums.length;
int fast = 0;//快指针从前往后遍历,找和val相同的数
int slow = 0;
//慢指针用来放数,fast遍历的数!=val,则需要放到慢指针处,=val,不需要,直接跳过
while(fast<n){
if(nums[fast] != val){
nums[slow] = nums[fast];
slow++;
}
fast++;
}
return slow;
}
}
T31:下一个排列
题目:

思路:


举例:截图摘自博主imageslr

代码:
class Solution {
public void nextPermutation(int[] nums) {
if(nums == null || nums.length<=1) return;
int len = nums.length;
for(int i=len-1;i>=1;i--){
//从后往前找到第一个升序对
if(nums[i]>nums[i-1]){
for(int j = len-1;j>=i;j--){
//从后往前找到第一个大于i-1的数,并交换
if(nums[j]>nums[i-1]){
swap(nums,j,i-1);
//升序i以及其后的数
//Arrays.sort(nums,i,len);//Arrays.sort(nums,i,j)是从i到j-1的元素排序
reverse(nums,i,len-1);
return;
}
}
}
}
//如果全都是降序,则直接全部升序
//Arrays.sort(nums,0,len);
reverse(nums,0,len-1);
}
public void swap(int[] nums,int a, int b){
int tmp = nums[a];
nums[a] = nums[b];
nums[b] = tmp;
}
public void reverse(int[] nums,int a,int b){
if(a<0 || b>= nums.length) return;
while(a<b){
swap(nums,a,b);
a++;
b--;
}
}
}
T32:最长有效括号
题目:

思路:栈


代码:
class Solution {
public int longestValidParentheses(String s) {
Deque<Integer> stack = new LinkedList<>();
stack.push(-1);
int res = 0;
for(int i=0;i<s.length();i++){
if(s.charAt(i) == '('){
stack.push(i);
}else{
stack.pop();
if(stack.isEmpty()) stack.push(i);
else res = Math.max(res,i-stack.peek());
}
}
return res;
}
}
T33:搜索旋转排序数组


class Solution {
public int search(int[] arr, int target) {
int start = 0;
int end = arr.length-1;
int mid;
if(arr == null || arr.length == 0) return -1;
if(arr.length == 1) return target==arr[0]? 0: -1;
while(start <= end){
mid = start+(end-start)/2;
if(arr[mid] == target) return mid;
if(arr[mid]>= arr[start]){//情形1:左边有序
if(target >= arr[start] && target < arr[mid]){//情形1.1:target位于左边
end = mid-1;//左边右届缩小
}else{//情形1.2:target位于右边
start = mid +1;//右边左届缩小
}
}else{//情形2:右边有序
if(target> arr[mid] && target <= arr[end]){//情形2.1:target位于右边
start = mid+1;//右边左届缩小
}else{//情形2.2:target位于左边
end = mid-1;//左边右届缩小
}
}
}
return -1;
}
}
T34:在排序数组中查找元素的第一个和最后一个位置
题目:

思路:
参考传送门,首先找到target-1的右边界,即target的起始第一个位置start,然后找到target的右边界,即target+1的起始位置xx,end=xx-1即target的最后一个位置,返回[start,end]
代码:
class Solution {
public int[] searchRange(int[] nums, int target) {
if(nums.length==0) return new int[]{-1,-1};
int start = helper(nums,target-1);//target第一个位置
int end = helper(nums,target)-1;//target最后一个位置
if(start>end) return new int[]{-1,-1};//说明没找到
return new int[]{start,end};
}
public int helper(int[] nums,int target){
int i=0;
int j=nums.length-1;
while(i<=j){
int mid = (i+j)>>1;
if(nums[mid]<=target){
i = mid+1;
}else{
j = mid-1;
}
}
return i;
}
}
T39: 组合总和
回溯类型:T39、T40、T46、T47,T77、T78、T90可以先看46、47的剪纸操作
题目:

思路:回溯法


代码:
1、朴素递归回溯,无剪枝
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> cur = new ArrayList<>();
dfs(candidates,target,res,cur,0);
return res;
}
public void dfs(int[] candidates,int target,List<List<Integer>> res, List<Integer> cur,int idx){
if(idx==candidates.length) return;
if(target == 0){
res.add(new ArrayList<>(cur));
return;
}
// 直接跳过
dfs(candidates,target,res,cur,idx+1);
// 选择当前数
if(target>=0){
cur.add(candidates[idx]);
dfs(candidates,target-candidates[idx],res,cur,idx);
cur.remove(cur.size()-1);
}
}
}
2、排序后递归------> 剪枝
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> cur = new ArrayList<>();
Arrays.sort(candidates);
dfs(candidates,target,res,cur,0);
return res;
}
public void dfs(int[] candidates,int target,List<List<Integer>> res, List<Integer> cur,int idx){
if(target<0) return;
if(target == 0){
res.add(new ArrayList<>(cur));
return;
}
for(int i = idx;i<candidates.length;i++){
if(candidates[idx]>target) break;//排序好后,比目标target大,直接减枝,降低时间复杂度
cur.add(candidates[i]);
dfs(candidates,target-candidates[i],res,cur,i);
//idx是确保从candidates数组的idx下标开始找数字的和,避免重复出现[2,2,3][2,3,2]的情况
//递归时去重,下次idx是从i开始,i之前的不考虑
//比如说在第一个遍历中用到了2,在后面3遍历的时候 就不会用2了。这样就可以去重
cur.remove(cur.size()-1);
}
}
}
T40:组合总和-||(不重复)【接上题】
回溯类型:T39、T40、T46、T47,T77、T78、T90可以先看46、47的剪纸操作

思路:
1、多加一个剪枝条件:if( i>0 && candidates[i]== candidates[i]-1) continue;
2、递归时用i+1,而不是i,因为不能包括重复数字
代码:
class Solution {
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> cur = new ArrayList<>();
Arrays.sort(candidates);
dfs(candidates,target,res,cur,0);
return res;
}
public void dfs(int[] candidates,int target,List<List<Integer>> res, List<Integer> cur,int idx){
if(target<0) return;
if(target == 0){
res.add(new ArrayList<>(cur));
return;
}
for(int i = idx;i<candidates.length;i++){
//1、大剪枝:减去 candidates[i] 小于 0,减去后面的 candidates[i + 1]、candidates[i + 2] 肯定也小于 0,因此用 break
if(candidates[idx]>target) break;//是break
//2、小剪枝:同一层相同数值的结点,从第 2 个开始,候选数更少,结果一定发生重复,因此跳过,用 continue
if(i>idx && candidates[i] == candidates[i-1]) continue;//是contionue
cur.add(candidates[i]);
//3、因为元素不可以重复使用,这里递归传递下去的是 i + 1 而不是 i
dfs(candidates,target-candidates[i],res,cur,i+1);
cur.remove(cur.size()-1);
}
}
}
T42:接雨水
题目:

思路:双指针

代码:
class Solution {
public int trap(int[] height) {
int left=0;//左指针,从左到右遍历
int right=height.length-1;//右指针,从右到左遍历
int left_max=0;//记录左边最大值
int right_max=0;//记录右边最大值
int res=0;//累计存水量
while(left<=right){
if(left_max<right_max){
res += Math.max(0,-height[left]);
//只有当height[left]低于left_max时,才会存水,大于的话不存水。下面会更新left_max
//因为height[left]>left_max的话,说明当前高度很高,旁边都都比他低,怎么存水。
//只有山谷才会存水,即中间低,两边高
left_max=Math.max(left_max,height[left]);
left++;
}else{
res += Math.max(0,right_max-height[right]);
//只有当height[right]低于right_max时,才会存水,大于的话不存水.下面会更新right_max
right_max=Math.max(right_max,height[right]);
right--;
}
}
return res;
}
}
T43:字符串相乘


主要是有大数
class Solution {
public String multiply(String num1, String num2) {
if(num1.equals("0") || num2.equals("0")) return "0";
String res = "0";
for(int i=num2.length()-1;i>=0;i--){
int carry = 0;
StringBuilder temp = new StringBuilder();//记录num2的当前i位上的数与nums1乘积的结果
//补0
for(int ii=0;ii<num2.length()-1-i;ii++){
temp.append(0);
}
int n2 = num2.charAt(i)-'0';//num2的当前i位上的数
//num2 的第 i 位数字 n2 与 num1 相乘
for(int j=num1.length()-1;j>=0 || carry!=0;j--){//∵存在j=-1(最低位前),但是进位carry上仍有数
int n1= j<0? 0 : num1.charAt(j)-'0';
int product = (carry+n1*n2)%10;//余数放到product
temp.append(product);
carry = (carry+n1*n2)/10;
}
res = addStrings(res,temp.reverse().toString());//将当前结果与新计算的结果求和作为新的结果
}
return res;
}
//对两个字符串数字进行相加,返回字符串形式的和
public String addStrings(String num1, String num2){
StringBuilder builder = new StringBuilder();
int carry=0;
for(int i=num1.length()-1,j=num2.length()-1;i>=0 || j>=0 || carry!=0;i--,j--){
int x = i<0? 0 : num1.charAt(i)-'0';
int y = j<0? 0 : num2.charAt(j)-'0';
int sum = (x+y+carry)%10;
builder.append(sum);
carry = (x+y+carry)/10;
}
return builder.reverse().toString();
}
}
T46:全排列(不重复)【回溯★、DFS★】
传送门:类似Offer38:字符串的排列
精华讲解回溯:@liweiwei1419
回溯类型:T39、T40、T46、T47,T77、T78、T90可以先看46、47的剪纸操作
题目:

思路:




【注意】


【演示回溯撤销当前选择】

代码:
class Solution {
public List<List<Integer>> permute(int[] nums) {
int len = nums.length;
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
boolean[] used = new boolean[len];
if(len ==0 ) return res;
dfs(nums,0,used,len,res,path);
return res;
}
public void dfs(int[] nums, int depth , boolean[] used, int len ,
List<List<Integer>> res, List<Integer> path){
if(depth == len){
res.add(new ArrayList<>(path));
return;
}
for(int i=0;i<len;i++){
// 在非叶子结点处,产生不同的分支,这一操作的语义是:
// 在还未选择的数中依次选择一个元素作为下一个位置的元素,这显然得通过一个循环实现。
if(!used[i]){
path.add(nums[i]);
used[i]=true;
dfs(nums,depth+1,used,len,res,path);
// 注意:下面这两行代码发生 「回溯」,
//回溯发生在从 深层结点 回到 浅层结点 的过程,代码在形式上和递归之前是对称的
used[i] = false;
path.remove(path.size()-1);
}
}
}
}
T47:全排列(重复)【接上题】
回溯类型:T39、T40、T46、T47,T77、T78、T90可以先看46、47的剪纸操作
思路:
在遍历的过程中,一边遍历一遍检测,在一定会产生重复结果集的地方剪枝。
【一定要先对数组排序】



代码:
class Solution {
public List<List<Integer>> permuteUnique(int[] nums) {
int len = nums.length;
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
boolean[] used = new boolean[len];
if(len ==0 ) return res;
Arrays.sort(nums);//先排序(升序或者降序都可以),排序是剪枝的前提
dfs(nums,0,used,len,res,path);
return res;
}
public void dfs(int[] nums, int depth , boolean[] used, int len ,
List<List<Integer>> res, List<Integer> path){
if(depth == len){
res.add(new ArrayList<>(path));
return;
}
for(int i=0;i<len;i++){
if(used[i]) continue;
// 剪枝条件:i > 0 是为了保证 nums[i - 1] 有意义
// 写 !used[i - 1] 是因为 nums[i - 1] 在深度优先遍历的过程中刚刚被撤销选择
if(i>0 && nums[i] == nums[i-1] && !used[i-1]){
continue;
}
path.add(nums[i]);
used[i]=true;
dfs(nums,depth+1,used,len,res,path);
used[i] = false;
path.remove(path.size()-1);
}
}
}
T48: 旋转图像
题目:
-
给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度
-
你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。

思路:
1、把矩阵按照一圈一圈处理,从最外圈开始,顺时针遍历,初始化最外圈的左上角和右下角元素。
2、最外圈从(0,0)开始依次遍历该行,直到该行最后一个元素,此时遍历了几个数,即每一圈的每一组需要遍历的次数。
3、旋转90°,即向顺时针瞬移的过程。即每一组里的数顺时针交换的过程,具体看下面手写过程。
4、每一组交换完,顺移下一组,直到这一圈结束。
5、左上角和右下角元素行列坐标向内缩减,向内一圈重复上述过程。
手写过程:

代码:
class Solution {
public void rotate(int[][] matrix) {
int Lr = 0;
int Lc = 0;
int Rr = matrix.length-1;
int Rc = matrix.length-1;
while(Lr < Rr){//外循环,有几层(圈)需要循环
swap(matrix, Lr++, Lc++, Rr--, Rc--);
//一圈执行完,指向内一圈,即原左上角数横纵坐标+1,原右下角数坐标-1
}
}
public void swap(int[][] matrix,int Lr,int Lc,int Rr,int Rc){
int times = Rc - Lc;//当前圈的列数-1
for (int i = 0; i < times; i++) {//每一圈有几组数需要顺移
int temp = matrix[Lr][Lc+i];
matrix[Lr][Lc+i] = matrix[Rr-i][Lc];//左上
matrix[Rr-i][Lc] = matrix[Rr][Rc-i];//左下
matrix[Rr][Rc-i] = matrix[Lr+i][Rc];//右下
matrix[Lr+i][Rc] = temp;//右上
}
}
}
T49:字母异位词分组
题目:

思路:排序 + 哈希表
【由于互为字母异位词的两个字符串包含的字母相同,因此对两个字符串分别进行排序之后得到的字符串一定是相同的,故可以将排序之后的字符串作为哈希表的键。】
1、首先对给的字符串数组strs中的每一个字符串str排序存到key,字母异位的字符串排序后的key肯定相同;
2、建一个哈希表map,键存放上述的key,值存这个key对应的字母异位的字符串列表list;
3、在遍历strs的每一个str时,当此时的key在map中有对应,说明有该字符串str的字母异位的字符串,则在此时的list后追加即可str即可;若没有,则新建一个list,以便后续添加使用,比如第一次。
4、返回map中的value,既是答案。
代码:
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String,List<String>> map = new HashMap<String,List<String>>();
for(String str : strs){
char[] array = str.toCharArray();
Arrays.sort(array);
String key = new String(array);//数组转字符串,key存放每类字母相同的字符串(排序后)
List<String> list = map.getOrDefault(key, new ArrayList<String>());
//获取map中指定key对应的value,没有则是新建一个string类型的列表,注意是列表;
//第一次时,没有key,则新建列表,后续只需在列表中添加即可
list.add(str);//把这个字符串添加到列表
map.put(key,list);//map更新列表
}
return new ArrayList<List<String>>(map.values());
}
}
T53:最大子序和
传送门:O42:连续子数组最大和
题目:

思路:动态规划

T54: 螺旋矩阵
给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
方法
1、把矩阵按照一圈一圈处理,先顺时针输出最外圈。初始化最外圈左上角和右下角元素坐标。
2、向内缩减一圈(左上和右下元素坐标向内移动),再次顺时针遍历
3、直到最后只剩下一列或者一行,则输出该列或者行
手写过程

代码
class Solution {
public List<Integer> spiralOrder(int[][] arr) {
int Lr = 0;
int Lc = 0;
int Rr = arr.length -1;
int Rc = arr[0].length-1;
List<Integer> list = new ArrayList<>();
while(Lr <= Rr && Lc <= Rc){
int cur_Lr = Lr;
int cur_Lc = Lc;
//顺时针遍历一圈
if(Lr == Rr){ //如果只剩下一行
for(int i= Lc;i<=Rc;i++){
list.add(arr[Lr][i]);
}
}else if(Lc == Rc){//如果只剩下一列
for(int i=Lr;i<=Rr;i++){
list.add(arr[i][Lc]);
}
}else{
while(cur_Lc != Rc){//先从左往右
list.add(arr[Lr][cur_Lc++]);
}
while(cur_Lr!=Rr){//再从上至下
list.add(arr[cur_Lr++][Rc]);
}
while(cur_Lc != Lc){//再从右向左
list.add(arr[Rr][cur_Lc--]);
}
while(cur_Lr != Lr){//再从下至上
list.add(arr[cur_Lr--][Lc]);
}
}
//整体向内移动一圈
Lr++;
Lc++;
Rr--;
Rc--;
//System.out.println(Lr+"/"+Lc+"/"+Rr+"/"+Rc);
}
return list;
}
}
T55:跳跃游戏
题目:

思路:贪心
遍历数组每个数,每次更新当前可到达的最远位置,即索引,如果遇到最远位置(索引)超过数组末尾索引,说明可以达到;如果遍历完了,最远位置的记录值小于最后一个位置,则不可达。【注意】最远位置即数组的索引值,而不是说当前数后移多少个位置。


代码:
class Solution {
public boolean canJump(int[] nums) {
int len = nums.length;
int maxPath = 0;
for(int i=0;i<len;i++){
if(i<=maxPath){
maxPath = Math.max(maxPath,i+nums[i]);
if(maxPath>=len-1){
return true;
}
}else return false;//如果索引都比当前最远可达位置大了,那肯定不可达
}
return false;
}
}
T56: 合并区间
题目:

思路:区间排序
1、先按照每个区间的左端点值升序排序
2、后一区间左端点<=当前区间右端点,说明重叠,更新当前右端点
3、后一区间左端点>当前区间右端点,无重叠,直接添加到res


代码:
class Solution {
public int[][] merge(int[][] intervals) {
List<int[]> res = new ArrayList<>();
if(intervals.length ==0 || intervals == null) return res.toArray(new int[0][]);
//根据每个数组左端点进行排序,,,排序是前提
Arrays.sort(intervals, (a,b) -> a[0]-b[0] );
int i=0;
while(i<intervals.length){
int left = intervals[i][0];
int right = intervals[i][1];//因为每个数组只有两个数,因此索引是0、1,对应区间左右端点
while(i<intervals.length-1 && intervals[i+1][0]<=right){
//因为已经排序好,当遇到下一数组左端点<=当前右端点,则说明下一数组区间和当前区间重叠;
//这里是while,遇到<= 的都要执行,直到不满足
i++;
right=Math.max(right,intervals[i][1]);//此时的i是+1后的i
}
//将现在的区间放进res
res.add(new int[]{left,right});
i++;//判断下一个区间
}
return res.toArray(new int[0][]);
}
}
T62: 不同路径
题目:

思路:动态规划
每次只能向右或向下走

优化:
cur[j] = cur[j-1] + cur[j]
未赋值之前右边的cur[j] 始终表示当前行第i行的上一行(i-1行)第j列的值,i
赋值之后左边的cur[j]表示当前行第i行第j列的值,cur[j-1] 表示当前行第i行第j-1列的值(cur[j-1] 在计算cur[j]之前就已经计算了,所以表示的是当前行而不是上一行 )
代码:
class Solution {
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
for(int i=0;i<m;i++){ dp[i][0] =1;}
for(int i=0;i<n;i++){ dp[0][i] =1;}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
dp[i][j] = dp[i][j-1] + dp[i-1][j];
}
}
return dp[m-1][n-1];
}
}
优化:
class Solution {
public int uniquePaths(int m, int n) {
int[] cur = new int[n];
Arrays.fill(cur,1);
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
cur[j] = cur[j] + cur[j-1];
//第一个cur[j]:当第i行j列的;第二个cur[j]:当前行的上一行和第j列的,即i-行,j列;
// cur[j-1]:当前行,j-1列,即i行j-1列
}
}
return cur[n-1];
}
}
T64:最小路径和
题目:

思路:动态规划

举例:

代码:
class Solution {
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(i == 0 && j == 0) continue;//初始化左边
if(j==0) grid[i][j] = grid[i][j] + grid[i-1][j];//左边界,即只能从上边过来,即i-1
else if(i==0) grid[i][j] = grid[i][j] + grid[i][j-1];//上边界,即只能从左边过来,即j-1
else grid[i][j] = grid[i][j] + Math.min(grid[i-1][j],grid[i][j-1]);
}
}
return grid[m-1][n-1];
}
}
T70:爬楼梯
传送门:Offer10-|| 青蛙跳台

T72:编辑距离
题目:

思路:动态规划


PS:代码中word1.charAt(i-1)==word2.charAt(j-1)的原因是:
初始化dp数组时dp[i][0]和dp[0][j]已经填写完成,所以接下来填表需要从1开始,
但是字符的比较需要从0开始,因此才这样写
代码:
class Solution {
public int minDistance(String word1, String word2) {
int n1 = word1.length();
int n2 = word2.length();
int[][] dp = new int[n1+1][n2+1];
for(int i=1;i<=n1;i++) dp[i][0]= dp[i-1][0]+1;
//此时n2为空,n1每次只需执行删除操作
for(int j=1;j<=n2;j++) dp[0][j]=dp[0][j-1]+1;
//此时n1为空,n1每次只需执行插入操作
for(int i=1;i<=n1;i++){
for(int j=1;j<=n2;j++){
if(word1.charAt(i-1) == word2.charAt(j-1)) dp[i][j] = dp[i-1][j-1];
else dp[i][j] = Math.min(Math.min(dp[i-1][j-1],dp[i-1][j]),dp[i][j-1])+1;
}
}
return dp[n1][n2];
}
}
T75:颜色分类
题目:

思路:双指针
当前数nums[i],遇到0放在数组头部,遇到2放到数组尾部;
1、用p0指针记录当前’0‘区间位置的后一个数,即当前0该放的位置;
2、用p2指针记录当前’2‘区间位置的前一个数,即当前2该放的位置;
3【注意】当nmus[i]是2,和nmus[p2]交换以后还是2,则需要继续交换;当交换来的是0,p0++,动图中显示交换的是0后还会和本身p0交换,是因为代码中如果当前是0,就需要和nmus[p0]交换,只不过此时i==p0;

举例:

代码:
class Solution {
public void sortColors(int[] nums) {
int p0=0;
int p2=nums.length-1;
for(int i =0;i<=p2;i++){
//如果nmus[i]==2
while(i<=p2 && nums[i]==2){
//如果nums[i]从nums[p2]交换来的数是2,则需要继续交换
int tmp = nums[p2];
nums[p2] = nums[i];
nums[i] = tmp;
p2--;
}
//如果nmus[i]==0
if(nums[i]==0){
int tmp = nums[p0];
nums[p0]= nums[i];
nums[i] = tmp;
p0++;
}
//如果nmus[i]==1:跳过这个数(跳出循环),即i++,
}
}
}
T76: 最小覆盖子串
题目:

思路:双指针[滑动窗口+哈希表]


具体:


代码:
class Solution {
public String minWindow(String s, String t) {
int n = s.length();
HashMap<Character, Integer> tMap = new HashMap<>();
HashMap<Character, Integer> windowMap = new HashMap<>();
for (char c : t.toCharArray()) { // 记录 t 中所有字符出现的次数
tMap.put(c, tMap.getOrDefault(c, 0) + 1);
}
int left = 0, right = 0;
// 记录窗口中满足条件的字符个数,t中字符的种类数
int flag = 0;
// 记录最小覆盖字串的起始索引及长度
int start = 0, minLength = Integer.MAX_VALUE;
while (right < n) {
char c = s.charAt(right);
// 判断取出的字符是否在 t 中
if (tMap.containsKey(c)) {
windowMap.put(c, windowMap.getOrDefault(c, 0) + 1);
// 判断取出的字符在窗口中的出现次数是否与 t 中该字符的出现次数相同
if (windowMap.get(c).equals(tMap.get(c))) {
flag++;
}
}
// 判断是否需要缩小窗口(已经找到符合条件的子串),即当前窗口第一个字符是否可以去掉
while (flag == tMap.size()) {//种类数一致,可考虑是否可以删除多余的字符
if (right - left + 1 < minLength) {
start = left;
minLength = right - left + 1;
}
char c1 = s.charAt(left);
left++;
if (tMap.containsKey(c1)) {
if (windowMap.get(c1).equals(tMap.get(c1))) {
flag--;
}
windowMap.put(c1, windowMap.getOrDefault(c1, 0) - 1);
}
}
right++;
}
return minLength == Integer.MAX_VALUE ? "" : s.substring(start, start + minLength);
}
}
T77:组合
回溯类型:T39、T40、T46、T47,T77、T78、T90可以先看46、47的剪纸操作
题目:

思路:回溯 + 剪纸


【剪纸】


代码:
public class Solution {
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> res = new ArrayList<>();
if (k <= 0 || n < k) {
return res;
}
// 从 1 开始是题目的设定
List<Integer> path = new ArrayList<>();
dfs(n, k, 1, path, res);
return res;
}
private void dfs(int n, int k, int idx, List<Integer> path, List<List<Integer>> res) {
// 递归终止条件是:path 的长度等于 k
if (path.size() == k) {
res.add(new ArrayList<>(path));
return;
}
// 遍历可能的搜索起点
for (int i = idx; i <= n-(k-path.size())+1; i++) {//剪纸
// 向路径变量里添加一个数
path.add(i);
// 下一轮搜索,设置的搜索起点要加 1,因为组合数理不允许出现重复的元素
dfs(n, k, i + 1, path, res);
// 重点理解这里:深度优先遍历有回头的过程,因此递归之前做了什么,递归之后需要做相同操作的逆向操作
path.remove(path.size()-1);
}
}
}
T78: 子集(无重复元素)
回溯类型:T39、T40、T46、T47,T77、T78、T90可以先看46、47的剪纸操作
题目:

思路:回溯
代码:
class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
if(nums.length==0) return res;
dfs(nums,res,path,0);
return res;
}
public void dfs(int[] nums,List<List<Integer>> res, List<Integer> path,int idx){
res.add(new ArrayList<>(path));
//此题 并不是==nmus.length()才添加,因为要考虑从1到nmus.length()每一种情况,所以每一种情况都添加
for(int i=idx;i<nums.length;i++){
path.add(nums[i]);
dfs(nums,res,path,i+1);
path.remove(path.size()-1);
}
}
}
T79:单词搜索
传送门:同O12:矩阵中的路径
题目:

T84:柱状图中最大的矩形
题目:

思路:单调栈
1、暴力解法:


2、+ 单调栈

上文中,定义了-1和8。表示当前柱子左侧/右侧最近的柱子没有比自己低的,故高度还是本身。

例如:[-1,0,-1,-1,3,4,5,3],当中的-1表示第0、2、3号柱子各自的左边(从左往右依次遍历时)都没有比自己低的柱子;[2,2,3,8,7,7,7,8]当中的8表示第7、3号柱子各自的右边(从右往左依次遍历时)都没有比自己低的柱子。
其他数字表示:例如[-1,0,-1,-1,3,4,5,3]中的0表示索引1号柱子左边离他最近比他低的柱子编号是0;5表示索引6号的柱子左边离他最近比他低的柱子编号是5;
例如[2,2,3,8,7,7,7,8]中的3表示索引2号的柱子右边最近比他低的是3号;
代码:
class Solution {
public int largestRectangleArea(int[] heights) {
int n = heights.length;
Stack<Integer> stack = new Stack<>();
int[] left = new int[n];//记录从左到右每个柱子的左边离他最近的比他低的柱子编号
int[] right = new int[n];//记录从右到左每个柱子的右边离他最近的比他低的柱子编号
//找左边的
for(int i =0;i<n;i++){
while(!stack.isEmpty() && heights[stack.peek()]>=heights[i]){
stack.pop();
}
left[i] = stack.isEmpty()? -1 : stack.peek();
stack.push(i);
}
stack.clear();
//找右边的
for(int i =n-1;i>=0;i--){
while(!stack.isEmpty() && heights[stack.peek()]>=heights[i]){
stack.pop();
}
right[i] = stack.isEmpty()? n : stack.peek();
stack.push(i);
}
//计算面积
int res =0;
for(int i=0;i<n;i++){
res=Math.max(res,(right[i]-left[i]-1)*heights[i]);//为什么是减1不是+1?
}
return res;
}
}
T85: 最大矩形
题目:

思路:

代码:
class Solution {
public int maximalRectangle(char[][] matrix) {
if(matrix.length==0) return 0;
int[] heights = new int[matrix[0].length];//记录matrix每一列连续是1的高度
int ans=0;
//计算每一列的高度,即连续是1的高度
for(int i=0;i<matrix.length;i++){
//遍历每一列,更新高度
for(int j=0;j<matrix[0].length;j++){
if(matrix[i][j] == '1'){
heights[j] += 1;
}else heights[j] =0;
//这里是清空heights[j],而不是会保留上次的值;因为高度需要连续,即连续的1,只要中间有一个是0,则高度从0开始重新累计
}
ans =Math.max(ans,largeRecetangle(heights));//将计算好的heights送到84题,求heights[]的最大矩形面积
}
return ans;
}
public int largeRecetangle(int[] heights){
int n = heights.length;
int[] left =new int[n];
int[] right =new int[n];
Stack<Integer> stack = new Stack<>();
for(int i=0;i<n;i++){
while(!stack.isEmpty() && heights[stack.peek()]>=heights[i]){
stack.pop();
}
left[i] = stack.isEmpty()? -1: stack.peek();
stack.push(i);
}
stack.clear();
for(int i=n-1;i>=0;i--){
while(!stack.isEmpty() && heights[stack.peek()]>=heights[i]){
stack.pop();
}
right[i] = stack.isEmpty()? n: stack.peek();
stack.push(i);
}
int res = 0;
for(int i=0;i<n;i++){
res = Math.max(res,(right[i]-left[i]-1)*heights[i]);
}
return res;
}
}
T90:子集-|| (含重复元素)
回溯类型:T39、T40、T46、T47,T77、T78、T90 可以先看46、47的剪纸操作
题目:

思路:回溯 + 剪纸
同上题,修改:
1、给数组排序(只有剪纸就一定要先排序)
2、当当前数和上一次选的数相等,说明重复,则剪纸
代码:
class Solution {
public List<List<Integer>> subsetsWithDup(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
if(nums.length==0) return res;
Arrays.sort(nums);//剪纸 就一定要记得给数组排序
dfs(nums,res,path,0);
return res;
}
public void dfs(int[] nums,List<List<Integer>> res, List<Integer> path,int idx){
res.add(new ArrayList<>(path));
for(int i=idx;i<nums.length;i++){
if(i>idx && nums[i]==nums[i-1]) continue;//剪纸
path.add(nums[i]);
dfs(nums,res,path,i+1);
path.remove(path.size()-1);
}
}
}
T94 \ T145 \T144: 二叉树的中序遍历(+后序+前序)
递归法:



T96:不同的二叉搜索树
题目:

思路:动态规划

代码:
class Solution {
public int numTrees(int n) {
int[] dp = new int[n+1];//多一个0;
dp[0]=1;//0个节点有1种排序,即空
dp[1]=1;//只有1个节点有1种排序,即1
for(int i=2;i<=n;i++){//i个节点共有dp[i]种排序法
for(int j=1;j<=i;j++){
//在这dp[i]种排法中,即从0~i中,叠加店铺dp[0]*dp[i-1]+dp[1]*dp[i-2]+...+dp[i-1]*dp[0];
dp[i] += dp[j-1]*dp[i-j];
}
}
return dp[n];
}
}
T98:验证二叉搜索树
题目:
思路:递归


代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public boolean isValidBST(TreeNode root) {
return isValidBST(root,Long.MIN_VALUE,Long.MAX_VALUE);//这里是Long型
}
public boolean isValidBST(TreeNode node, long left_min ,long right_max){
if(node == null) return true;
if(node.val<=left_min || node.val>=right_max) return false;
return isValidBST(node.left,left_min,node.val) && isValidBST(node.right,node.val,right_max);
//这里是&&,需要同时满足;比如左子树是空,但右子树不空时
}
}
T101: 对称二叉树
传送门:O28:对称二叉树

T102:二叉树的层序遍历
题目:

思路:

在 while 循环的每一轮中,都是将当前层的【每一个】结点依次出队列,再将该节点下一层的左右结点入队列,这样就实现了层序遍历。比如:第一层有2、3,先让2出了队列,再让2的4、5入队,然后3出队,让6入队
例如上述
1)、1先入队。第0层只有1个数,n=1,队列出1,list加1,res=[[1]];队列加入1的2、3
2)、这一层有2个数,n=2、首先,队列头部2出队列,list加2,队列加入4、5;其次队列出3,list加3,队列加入6,此时队列有4、5、6。res=[ [1],[2,3] ]
3)这一层有3个数,n=3、首先,队列出4,list加4;其次队列出5,list加5,队列加入7;接着队列出6,list加6,队列入8、9;此时,队列有7、8、9;res=[ [1],[2,3],[4,5,6] ]
…
代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if(root==null) return res;
Deque<TreeNode> deque = new ArrayDeque<>();//通过入队出队来控制 当前节点应该属于第几层
deque.add(root);
while(!deque.isEmpty()){
int n = deque.size();//记录当前队列的尺寸,即当前层节点的个数
List<Integer> list = new ArrayList<>();//存储当前层节点的列表
for(int i=0;i<n;i++){//遍历该层的所有节点
TreeNode node = deque.poll();//该层当前节点值出队列;deque.poll():获取队列【头部】元素并删除;
list.add(node.val);//列表加入这个节点值,加到队列【尾部】
if(node.left!=null) deque.add(node.left);//队列加入出队节点的左孩子
if(node.right!=null) deque.add(node.right);//队列加入出队节点的右孩子
//下次出队列时左孩子先出,因为poll()删除头部,左孩先进在头部。【先进先出】
}
res.add(list);
}
return res;
}
}
T104: 二叉树的最大深度
传送门:O55
T105: 从前序中序遍历构造二叉树
传送门:O7:重建二叉树

T106: 从中序和后序遍历构造二叉树
思路同上题,只是将前序改为后序,根节点的位置变成最后,需要计算右子树长度;上一题是计算左子树长度

T114:二叉树展开为链表

T121: 买卖股票的最佳时机
传送门 :O63:股票的最大利润
T124:二叉树中最大路径和
题目:

思路:递归
1、首先求二叉树中每个节点的最大贡献值
2、求二叉树中任一个节点的最大路径和

举例:


代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int maxSum = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
MaxGain(root);
return maxSum;
}
public int MaxGain(TreeNode node){
if (node == null) return 0;
//递归计算【左右子节点】最大贡献值,只有最大贡献值>0,才会选择【该节点】
int left_Gain = Math.max(MaxGain(node.left),0);
int right_Gain = Math.max(MaxGain(node.right),0);
//【该节点】的最大路径和=该节点值+左和右节点的最大贡献值
int max_path_sum = node.val+left_Gain+right_Gain;
//更新答案
maxSum = Math.max(maxSum,max_path_sum);
//递归结束返回【该节点】的最大贡献值
return node.val+Math.max(left_Gain,right_Gain);
}
}
T125:验证回文串
题目:

思路:双指针

代码:
class Solution {
public boolean isPalindrome(String s) {
int n = s.length();
int left =0;
int right=n-1;
while(left<right){//左右双指针向中心走,每次判断所指元素是否相等
//跳过非字符元素
//Character.isLetterOrDigit 确定字符是否为字母或数字
while(left<right && !Character.isLetterOrDigit(s.charAt(left))){
left++;
}
while(left<right && !Character.isLetterOrDigit(s.charAt(right))){
right--;
}
//判断是否相同,Character.toLowerCase转换成小写
while(left<right && Character.toLowerCase(s.charAt(left))
!=Character.toLowerCase(s.charAt(right))){
return false;
}
left++;
right--;
}
return true;
}
}
T128: 最长子序列
题目:

思路:哈希表
用哈希表【键】:存每个数
用哈希表【值】:存每个数所在的连续区间的【长度】
例如: hashmap中存放的【键】分别是1234678,每个数对应的【值】:4、4、4、4、3、3、3(∵其中1234连续,该连续区间长度是【4】;678连续,长度是【3】)把1和4称为区间[1,4]的【端点】;6和8称为[6,8]的【端点】
- 1、定义一个哈希表(用于判断某个数是否存在哈希表中),然后遍历数组,如果当前数num存在哈希表,那么跳过;如果当前数num不存在哈希表中,那么查找num-1和num+1这两个数是不是在哈希表中
- 2、比如上述的1234678,当num=5。5进来之后,看看4和6在不在哈希表内,如果在的话,4和6一定是端点,可以连成连续区间;所以5进来之后,发现左边有4个连续的,右边有3个连续的,加上自己一个,那么组成一个大连续的4+3+1
= 8 - 3、更新当前最长连续串的【端点】的值value(也就是1的位置(1=5-4),8的位置(8=5+3)),更新为8。只需端点更新值就行,因为端点之间的数在遍历的时候如果在哈希表中就会略过
- 4、如果这个时候9又进来,那么它获取到数8的值是8,10的值是0,更新连续子串长度(8+1+0=9),所以更新左端点1(1=9-8)的值为9,右端点9(9=9+0)的值为9。
代码:
class Solution {
public int longestConsecutive(int[] nums) {
HashMap<Integer,Integer> map = new HashMap<>();
int n=nums.length;
int res = 0;
for(int num :nums){
if(!map.containsKey(num)){
int left = map.get(num-1)==null?0 : map.get(num-1);
//num-1 存在,左端点()即使num-1)对应的【值】,即num-1所在连续区间的长度
int right= map.get(num+1)==null?0 : map.get(num+1);
//num+1 存在,右端点(即使num+1)对应的【值】,即num+1所在连续区间的长度
int cur = left+right+1;//计算当前的连续子串长度
if(cur>res) res = cur;
map.put(num,cur);//放当前数
map.put(num-left,cur);//更新左边端点所在区间的长度
map.put(num+right,cur);//更新右边端点所在区间的长度
}
}
return res;
}
}
T130:被围绕的区域



class Solution {
int[] dx = {1,-1,0,0};//下,上,
int[] dy = {0,0,1,-1};//右,左
public void solve(char[][] board) {
int n = board.length;
int m = board[0].length;
if(n==0) return;
Queue<int[]> queue = new LinkedList<int[]>();
for(int i=0;i<n;i++){//列边界上的0
if(board[i][0]=='O'){
queue.offer(new int[]{i,0});
board[i][0]='A';
}
if(board[i][m-1]=='O'){
queue.offer(new int[]{i,m-1});
board[i][m-1]='A';
}
}
for(int j=0;j<m;j++){//行边界上的0
if(board[0][j]=='O'){
queue.offer(new int[]{0,j});
board[0][j]='A';
}
if(board[n-1][j]=='O'){
queue.offer(new int[]{n-1,j});
board[n-1][j]='A';
}
}
while(!queue.isEmpty()){//搜索每个边界上的0 的直接或间接相邻的0 标记位A
int[] tmp = queue.poll();
int x = tmp[0], y =tmp[1];
for(int i=0;i<4;i++){
int mx = x+dx[i], my = y+dy[i];
if(mx<0 || my<0 || mx>=n || my >=m || board[mx][my]!='O') continue;
queue.offer(new int[]{mx,my});
board[mx][my]='A';
}
}
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(board[i][j]=='A') board[i][j]='O';
else if (board[i][j] == 'O') board[i][j]='X';
}
}
}
}
T136:只出现1次的数字
题目:

思路:异或

代码:
class Solution {
public int singleNumber(int[] nums) {
int res=0;
for(int num : nums){
res ^= num;
}
return res;
}
}
T139:单词拆分
题目:

思路:动态规划
看字符串s的(0,n-1)是否可以被字典表示
- 1、遍历字符串,i∈(0,n-1), 看当前的(0,i)子串是否可以被表示,直到i=n-1
- 2、看当前的子串s(0,i)是否被表示:把子串s(0,i)拆分成s1(0,j-1)和s2(j,i-1)两部分,j∈(0,i-1),看s1和s2是否可以被表示
- 3、其中s1(0,j-1)物理意义对应dp[j],j∈(0,i-1),在计算dp[i]时是已知的
- 4、计算s2(j,i-1)对应的check(s[j,i-1])

代码:
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> wordDictSet = new HashSet<>(wordDict);
int n = s.length();
boolean[] dp = new boolean[n+1];//多一个0
dp[0] = true;
for(int i=1;i<=n;i++){
for(int j=0;j<i;j++){
//当前的子串(0,i)只要其中存在匹配的s1和s2,则dp[i]就true
if (dp[j] && wordDictSet.contains(s.substring(j, i))) {
dp[i] = true;
break;
}
}
}
return dp[n];
}
}
T141:环形链表
题目:

思路:快慢指针追赶
快指针每次走两步,慢指针每次一步;
当一个链表有环时,快慢指针都会陷入环中进行无限次移动,然后变成了追及问题。想象一下在操场跑步的场景,只要一直跑下去,快的总会追上慢的。当两个指针都进入环后,每轮移动使得慢指针到快指针的距离增加一,同时快指针到慢指针的距离也减少一,只要一直移动下去,【快针总会追上慢指针】。
代码:
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
if(head==null || head.next==null) return false;
ListNode slow = head;
ListNode fast = head.next;
while(slow!=fast){
if(fast==null || fast.next==null) return false;
slow = slow.next;
fast = fast.next.next;
}
return true;
}
}
T142:环形链表||
题目:
本题和上一题区别:不仅要判断是否有环,还要判断输出环的入口位置
思路:快慢指针
TIPS: 若有环,两指针一定会相遇。因为每走 11 轮,fast 与 slow 的间距 +1+1,fast 终会追上 slow;
-
原理:
快指针fast走的路程设为f,慢指针slow走的路程是s;设链表共有 a+b个节点,b是换的节点个数,那么有:
1、f=2s(因为两者同时出发,slow每次走1步,fast每次走2步,不论何时,f=2s)
2、f=s+nb(相遇时,刚好fast多走了n圈)
–>>推论 s=nb,即两者相遇时slow走的步数是nb

-
过程:
1、构建第一次相遇:此时slow走了nb,即s=nb
2、找到环入口点:因为链表长为a+b,此时slow走了nb,当slow走到环入口时刚好把链表走了一遍,因此slow再走a步就到达入口,所有走到链表入口节点时的步数是:a+nb。假设slow再走a步就到达环入口点,而flow也刚好达到入口点,两者相遇。那么需要求a。
3、在slow第一次相遇在nb后,将快指针放到链表头节点,然后和slow同时走,并且每次改为走一步,当相遇时fast走la步。【当 fast 指针走到f = a步时,slow 指针走到步s = a+nb,此时 两指针重合,并同时指向链表环入口 】

代码:
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while(true){//找到第一次相遇的slow位置
if(fast == null || fast.next==null) return null;
slow = slow.next;
fast = fast.next.next;
if(slow==fast) break;
}
fast = head;//将fast放置到head重新走,步数改为1
while(slow!=fast){//相等时,则是环入口
slow = slow.next;
fast = fast.next;//该步数
}
return fast;
}
}
T146: LRU缓存机制
题目:

思路:哈希表+双向链表



代码:
class LRUCache {
class Node{
int key;
int value;
Node pre;
Node next;
public Node(){}
public Node(int k,int v){key = k; value=v;}
}
private Map<Integer,Node> map = new HashMap<>();
private int capacity;
private int size;
private Node head,tail;//伪头部和尾节点
public LRUCache(int capacity) {
this.size=0;
this.capacity = capacity;
//引入伪节点,并连接在一起
head = new Node();
tail = new Node();
head.next=tail;
tail.pre=head;
}
public int get(int key) {
Node cur = map.get(key);//cur是当前key在哈希表中对应的节点
if(cur==null) return -1;
//如果key存在,通过哈希表定位,在移动到头部
moveTohead(cur);
return cur.value;//这里是value不是key,cur是节点,返回该节点的value
}
public void put(int key, int value) {
Node cur = map.get(key);
if(cur==null){
//如果不存在,创建一个新节点
Node newnode = new Node(key,value);
//添加到哈希表
map.put(key,newnode);
//添加到双向链表头部
addTohead(newnode);
size++;
if(size>capacity){//如果超出容量。删除链表尾节点
Node tail = removeTail();
map.remove(tail.key);//删除哈希表中对应的key
size--;
}
}else{//如果存在,先通过哈希表定位,再修改value,再加到头部
cur.value=value;
moveTohead(cur);
}
}
void remove(Node node){//删除节点
node.pre.next = node.next;
node.next.pre = node.pre;
}
void addTohead(Node node){
node.pre = head;
node.next=head.next;
head.next.pre=node;
head.next=node;
}
void moveTohead(Node node){//移到头部
remove(node);//先在原先位置删除该节点
addTohead(node);//再向头部添加该节点
}
Node removeTail(){//删除链表的真尾部节点,并返回该节点
Node realTail = tail.pre;
remove(realTail);
return realTail;
}
}
/**
* 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);
*/
T148:排序链表
题目:

思路:归并排序


代码:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode sortList(ListNode head) {
//递归结束条件
if(head==null || head.next==null) return head;
ListNode midNode = FindMid(head);//找中间节点
ListNode rightHead = midNode.next;//存储mid后面的节点们
midNode.next=null;//切断中点后面的节点
ListNode left = sortList(head);//递归左边
ListNode right = sortList(rightHead);//递归右边
return mergeTwoList(left,right);//归并
}
public ListNode FindMid(ListNode head){//找链表中间点
if(head==null || head.next==null) return head;
ListNode slow = head;
ListNode fast = head.next;//这里也可写为head.next.next,和while里条件有关
while(fast!=null && fast.next!=null){
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
public ListNode mergeTwoList(ListNode left, ListNode right){//合并两个有序链表
ListNode res = new ListNode(-1);
ListNode cur = res;
while(left!=null && right !=null ){
if(left.val<right.val){
cur.next=left;
left=left.next;
}else{
cur.next=right;
right=right.next;
}
cur=cur.next;
}
cur.next = left!=null? left : right;
return res.next;
}
}
T152: 乘积最大子数组
题目:

思路:

代码:
class Solution {
public int maxProduct(int[] nums) {
int max=Integer.MIN_VALUE;
int imax=1;
int imin=1;//因为是乘积,所以初始为1
for(int i=0;i<nums.length;i++){
if(nums[i]<0){//是负数
int tmp = imax;
imax=Math.max(imin*nums[i],nums[i]);
imin=Math.min(tmp*nums[i],nums[i]);
}else{
imax=Math.max(imax*nums[i],nums[i]);
imin=Math.min(imin*nums[i],nums[i]);
}
max=Math.max(max,imax);
}
return max;
}
}
T152: 最小栈
传送门:O30栈最小元素
T155题目: 最小栈
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
- push(x) —— 将元素 x 推入栈中。
- pop() —— 删除栈顶的元素。
- top() —— 获取栈顶元素。
- getMin() —— 检索栈中的最小元素。
方法
创建两个栈,一个存数据,一个存数据的最小值。
- stackData存储数据,stackMin存储stackData的最小值,且stackMin栈顶永远存储当前stackData栈的最小值
- stackData每压入一个数newNum,stackMin也压入一个数x,x要不等于newNum,要不就是上次压入的值
- 如果stackMin为空,就直接压入newNum;否则比较newNum与当前stackMin栈顶的大小
- 如果newNum小于当前栈顶,x=newNum,否则(说明newNum比上次压入的数还大)继续压入上一次压入的数(即当前栈顶);
- stackData每弹出一个数,同时弹出stackMin的栈顶
手写过程

代码
public class GetMinStack {
private Stack<Integer> stackData;
private Stack<Integer> stackMin;
public GetMinStack(){
this.stackData = new Stack<>();
this.stackMin = new Stack<>();
}
public void push(int newNum){
this.stackData.push(newNum);
if(this.stackMin.isEmpty() || newNum <=this.stackMin.peek()){
this.stackMin.push(newNum);
}else{
this.stackMin.push(this.stackMin.peek());
// 如果输入的数大于当前辅助栈顶,则辅助栈压入上一次压入的最小值(栈顶)
}
}
public int pop(){
if(this.stackData.isEmpty()){
throw new RuntimeException("The stack is empty");
// 因为数据栈和辅助栈同时入栈、出栈,故长度保持一致,你空它也空
} else{//两栈均不为空
this.stackMin.pop();
return this.stackData.pop();
}
}
public int top(){
return this.stackData.peek();
}
public int getMin(){
if(this.stackMin.isEmpty()){
throw new RuntimeException("the stack is empty");
}
return this.stackMin.peek();
}
T160: 相交链表
传送门:O52两链表第一个公共节点
T164: MaxGap 最大间距
给定一个数组,求如果排序之后,相邻两数的最大差值,要求时间复杂度O(N),且不能用非基于比较的排序
方法
借用桶排序思想可巧妙解决该问题。无需对数组排序即可找到最大差值。
代码
public static int maxGap(int[] arr){
if(arr == null || arr.length<2){
return 0;
}
int len = arr.length;
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for (int i = 0; i < len; i++) {
min = Math.min(min,arr[i]);
max = Math.max(max,arr[i]);
}
if(min == max){//说明数组里的数都是同一个数
return 0; }
//桶里只记录 1、最小值 2、最大值 3、当前桶里是否有数
boolean[] hasNum = new boolean[len+1];
int[] mins = new int[len+1];
int[] maxs = new int[len+1];
int bid = 0;//桶的编号,从0开始
//把数组的数依次放入对应的桶里
for (int i = 0; i < len; i++) {
bid = bucket(arr[i],len,min,max);
//更新桶的最小值与最大值
mins[bid] = hasNum[bid] ? Math.min(mins[bid],arr[i]) : arr[i];
maxs[bid] = hasNum[bid] ? Math.max(maxs[bid],arr[i]) : arr[i];
hasNum[bid] = true;//此时,桶内进数了 状态变为true
}
int res = 0; //记录最大差值
int lastMax = maxs[0]; // 初始化前桶的最大值
int i = 1;
for ( ; i<= len ; i++) { // 从i=1 开始的桶计算与前筒的差值
if(hasNum[i]){//如果是空的直接跳过
res = Math.max(res,mins[i] - lastMax);
//后非空桶的最小值减去前非空桶最大值(数组相邻两数的差值),赋给res
lastMax = maxs[i];//桶向后移动
}
}
return res;
}
// 数num应该入几号桶
public static int bucket(long num, long len ,long min, long max){
// 防止(num-min)*len 溢出 转为long型
return (int)((num-min)*len/(max-min));
}
T169:多数元素
传送门:O39数组出现超过一半的数字
T179:最大数(剑指46)
剑指46:把数组排成最小的数。原理一样
思路

主要思想:字符串拼接比较

代码
class Solution {
public String largestNumber(int[] nums) {
int n =nums.length;
String[] arr = new String[n];
for(int i=0;i<n;i++){
arr[i] = String.valueOf(nums[i]);
}
//对数组arr按照拼接后的大小进行降序排列
//通过比较(a+b)和(b+a)的大小,就可以判断出a,b两个字符串谁应该在前面
//eg,[3,30,34]排序后变为[34,3,30];[233,23333]排序后变为[23333,233]
Arrays.sort(arr,(a,b)->{
return (b+a).compareTo(a+b);
});
//如果排序后的第一个元素是0,那后面的元素肯定小于或等于0,则可直接返回0
//但要注意equals和==的区别,前者判断值是否相等,后者判断引用地址是否相等
if(arr[0].equals("0")) return "0";
StringBuilder res = new StringBuilder();
for(int i=0;i<n;i++){
res.append(arr[i]);
}
return res.toString();
}
}
T198:打家劫舍|

T213:打家劫舍||
- 和T198相比(首尾不相邻),只是多了选大小的语句
- 首尾相邻,结果只有两种情况;
1、选第一个,最后一个不选,那么在【0,n-2】区间进行T198的计算
2、不选第一个,最后一个选,那么在【1,n-1】区间进行T198的计算 - 然后针对1和2的结果,选择大的那个即可

T337: 打家劫舍 III
题目:

思路:

代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int rob(TreeNode root) {
int[] res = dfs(root);
return Math.max(res[0],res[1]);
}
public int[] dfs(TreeNode node){
if(node==null) return new int[]{0,0};
// 由于需要后序遍历,所以先计算左右子结点,然后计算当前结点的状态值
int[] left = dfs(node.left);
int[] right = dfs(node.right);
int select = node.val+left[1]+right[1];//选择当前节点,则它的左右孩子都不能选
int NotSelect = Math.max(left[0],left[1])+Math.max(right[0],right[1]);
//不选该节点,它的左右孩子可以选,而且可以都选
return new int[]{select,NotSelect};
}
}
T200:岛屿数量
题目:

思路:深度优先dfs

代码:
class Solution {
public int numIslands(char[][] grid) {
int count =0;
for(int i=0;i<grid.length;i++){
for(int j=0;j<grid[0].length;j++){
if(grid[i][j]=='1'){
dfs(grid,i,j);
count++;
}
}
}
return count;
}
public void dfs(char[][] grid,int i,int j){
if(i<0 || j<0 || i>=grid.length|| j >=grid[0].length || grid[i][j]== '0') return;
grid[i][j]='0';//考虑到连续岛屿,即连续的1
dfs(grid,i+1,j);
dfs(grid,i,j+1);
dfs(grid,i,j-1);
dfs(grid,i-1,j);
}
}
T206:反转链表
题目:

思路:
- 1、利用栈先进后出
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
if(head==null || head.next==null) return head;
Stack<ListNode> stack = new Stack<>();
ListNode newNode = new ListNode(0);
ListNode cur = newNode;
while(head!=null){
stack.push(head);
head=head.next;
}
while(!stack.isEmpty()){
cur.next=stack.pop();
cur = cur.next;
}
cur.next=null;//要加的,因为最后一次执行完有cur后移,见while里的cur = cur.next;
return newNode.next;
}
}
- 2、递归+双指针

class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
while(cur!=null){
ListNode next = cur.next;
cur.next=pre;
pre=cur;
cur=next;
}
return pre;
}
}
T207: 课程表
题目:

思路:有向无环图+dfs

倒着来,先从先修课开始,判断当前先修课学完可以学哪些课程aa,然后学完这些课程aa后就可以再学习哪些课程bb…,使得这条路不能成环




代码:
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
//res来存储每一个课程对应的路是否有环
List<List<Integer>> res = new ArrayList<>();
for(int i=0;i<numCourses;i++){
res.add(new ArrayList<>());
}
for(int[] cp : prerequisites){
res.get(cp[1]).add(cp[0]);
//邻接表,将每个先修课程cp[1]上完可以上的课程cp[0]加到这个先修课程的列表里
}
int[] flags = new int[numCourses];//记录当前课程的状态
for(int i=0;i<numCourses;i++){//从第一个课程开始dfs,判断以该课程开始的路是否有环
if(!dfs(res,flags,i)) return false;
}
return true;
}
public boolean dfs(List<List<Integer>> res,int[] flags,int i){
if(flags[i]==-1) return true;//其他节点启动的 DFS 访问过了,路径没问题,不需要再访问了
if(flags[i]==1) return false;//本节点启动的 DFS 访问过了,一旦遇到了即二次访问,也说明有环了
flags[i] = 1;//当前访问完的节点置1
for(int j : res.get(i)){//对当前课程res.get(i)上万才可以上的课程依次进行深度搜索
if(!dfs(res,flags,j)) return false;
}
flags[i]=-1;//当前节点的后续课程遍历完发现无环,将该点的flag置-1
return true;
}
}
T208:实现Trie(前缀树)
题目:

思路:


代码:
class Trie {//看成是每个节点有26个节点的多叉树
TrieNode root;
class TrieNode{
boolean isExist = false;
TrieNode[] next = new TrieNode[26];
}
/** Initialize your data structure here. */
public Trie() {
root = new TrieNode();
}
/** Inserts a word into the trie. */
public void insert(String word) {
TrieNode cur_node = root;
for(int i=0;i<word.length();i++){
int index = word.charAt(i) - 'a';//当前字母对应的节点索引
if(cur_node.next[index] == null) cur_node.next[index] = new TrieNode();
cur_node = cur_node.next[index];
}
cur_node.isExist = true;//修改加入的单词状态
}
/** Returns if the word is in the trie. */
public boolean search(String word) {
TrieNode cur_node = root;
for(int i = 0;i<word.length();i++){
int index = word.charAt(i) - 'a';//当前字母对应的节点索引
if(cur_node.next[index] == null) return false;
cur_node = cur_node.next[index];
}
return cur_node.isExist;//即使该单词在列表,但是可能是别的单词的前缀,即它的isExist是false
}
/** Returns if there is any word in the trie that starts with the given prefix. */
public boolean startsWith(String prefix) {
TrieNode cur_node = root;
for(int i = 0;i<prefix.length();i++){
int index = prefix.charAt(i) - 'a';//当前字母对应的节点索引
if(cur_node.next[index] == null) return false;
cur_node = cur_node.next[index];
}
return true;//循环结束未返回false 说明包含该前缀
}
}
T210:课程表||




入度:指向当前节点的边,有几条入度就是几
class Solution {
List<List<Integer>> edegs;//存储有向图
int[] indeg;//存储每个节点的入度(即指向该节点的点的个数)
int[] res;//存储答案
int index;//答案的下标,用来循环后移
public int[] findOrder(int numCourses, int[][] prerequisites) {
edegs = new ArrayList<List<Integer>>();
for(int i=0;i<numCourses;i++){
edegs.add(new ArrayList<>());//初始化,先给每个点创造边
}
indeg = new int[numCourses];
res = new int[numCourses];
index = 0;
for(int[] info : prerequisites){
//info存储每个点的矩阵[a,b],a表示当前课程,b表示a前面需有的课
edegs.get(info[1]).add(info[0]);
//给当前点的须有点b(info[1])添加边,指向当前点a(info[0])
indeg[info[0]]++;//当前点的入度+1
}
Queue<Integer> queue = new LinkedList<Integer>();
//将所有入度为0的节点添加到队列当中
for(int i=0;i<numCourses;i++){//∵拓扑排序有多种结果,所以这些节点入队的顺序没有要求
if(indeg[i]==0) queue.offer(i);
}
while(!queue.isEmpty()){
int u = queue.poll();//从队首取出一个节点
res[index++] = u;//加入到结果集
for(int v : edegs.get(u)){//找到u节点的邻接点v
indeg[v]--;//v的入度-1
if(indeg[v] == 0){
queue.offer(v);
}
}
}
if(index!=numCourses) return new int[0];
return res;
}
}
T215: 数组中的第K个最大元素
传送门:O40最小的k个数
T221: 最大正方形
题目:

思路:动态规划
d[i, j] 代表的是,以坐标点(i,j) 为右下角的最大正方形

状态转移方程中为什么选择较小的:如下

代码:
class Solution {
public int maximalSquare(char[][] matrix) {
int maxSide =0;//记录最大正方形的边长
int m = matrix.length;
int n= matrix[0].length;
int[][] dp = new int[m][n];
if(m==0 || n==0 || matrix==null) return maxSide;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(matrix[i][j]=='1'){///注意给的是字符数组而不是整形数组
if(i==0 || j==0) dp[i][j] =1;
else{
dp[i][j] = Math.min(dp[i][j-1],Math.min(dp[i-1][j],dp[i-1][j-1]))+1;
}
maxSide=Math.max(maxSide,dp[i][j]); //记录最大的
}
}
}
int max_area = maxSide*maxSide;//边长平方=面积
return max_area;
}
}
T226:翻转二叉树
传送门:O27:二叉树的镜像
T232: 用栈实现队列
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
- void push(int x) 将元素 x 推到队列的末尾
- int pop() 从队列的开头移除并返回元素
- int peek() 返回队列开头的元素
- boolean empty() 如果队列为空,返回 true ;否则,返回 false
方法
- 用两个栈实现队列功能,即先进后出变为先进先出
- 只放数的栈push,只取数的栈pop,因为先放的数在push栈的最下面,故需要将push栈的数从顶至下依次弹出到pop栈
1、 push最上面的数先进pop栈放在pop最下
2、 push栈最下面的数后进pop栈放到pop最上面
3、 pop栈弹出栈顶数,即取出pusn栈最下面的数
手写过程

代码
class MyQueue {
private Stack<Integer> StackPush = new Stack<>();
private Stack<Integer> StackPop = new Stack<>();
/** Initialize your data structure here. */
public MyQueue() {
}
/** Push element x to the back of queue. */
public void push(int x) {
StackPush.push(x);
}
/** Removes the element from in front of queue and returns that element. */
public int pop() {//从队列的开头移除并返回这个元素
if(empty()){
throw new RuntimeException("empty");
}else if(StackPop.empty()){ // 如果pop栈空,则执行,非空直接取出pop栈顶数
while(!StackPush.empty()){ //条件:要到就要全倒完,直到push栈为空
StackPop.push(StackPush.pop()); //将push栈的倒入pop栈,即可实现数据顺序的倒转
}
}
return StackPop.pop(); //移除pop栈顶并返回这个数
}
/** Get the front element. */
public int peek() { // 只是返回队列开头的元素,不移除
if(empty()){
throw new RuntimeException("empty");
}else if(StackPop.empty()){//如果pop栈空,则执行,非空直接返回栈顶数
while(!StackPush.empty()){
StackPop.push(StackPush.pop());
}
}
return StackPop.peek(); //取出pop栈顶的数。前提:pop栈有数,没数的话先从push栈往进倒数,等push全倒完再从pop取
}
/** Returns whether the queue is empty. */
public boolean empty() {
return StackPush.isEmpty() && StackPop.isEmpty();
}
}
T234:回文链表
方法一:利用栈结构
- 将链表从中间点折两半,后半依次放入栈中;
- 然后从链表的第一个元素开始和出栈的元素依次相比
class Solution {
public boolean isPalindrome(ListNode head) {
if(head == null || head.next == null){
return true;
}
ListNode n1 = head;
ListNode n2 = head;
while(n2.next != null && n2.next.next != null){//找中间点
n1 = n1.next;//慢指针最终指向中点
n2 = n2.next.next;
}
Stack<ListNode> stack = new Stack();
while(n1.next != null){//从中间点后的元素开始入栈
stack.push(n1.next);
n1 = n1.next;
}
while(!stack.isEmpty()){// 出栈比较
if(head.val != stack.pop().val){
return false;
}
head = head.next;
}
return true;
}
}
方法二:不用栈结构(更高效)
- 反转右半链表,最后记得再反转回来奥

// 方法二:不利用栈
class Solution {
public boolean isPalindrome(ListNode head) {
if(head == null || head.next == null){
return true;
}
ListNode n1 = head;
ListNode n2 = head;
while(n2.next != null && n2.next.next != null){//找中间点
n1 = n1.next;//慢指针最终指向中点
n2 = n2.next.next;
}
n2 = n1.next; // n2指向右半第一个点,n2可以看为tmp/cur,只是避免定义新的LiatNode,所以废物利用
n1.next = null;
ListNode n3 = null;
while(n2 != null){//反转右半链表
n3 = n2.next;//n3临时变量
n2.next = n1;//反转
n1 = n2;// n1后移,指向当前右半前一个点
n2 = n3;// n2 后移,指向当前右半第一个点
}
n3 = n1;//循环完n1指向最后一个点 废物利用
n2 = head;//废物利用
boolean res = true;
while(n1 != null && n2 != null){
if(n1.val != n2.val){
res = false;
break;
}
n1 = n1.next;
n2 = n2.next;
}
//恢复右半反转的链表
n1 = n3.next; // n1指向当前最后一个点的前面
n3.next = null;// 最后一个点指向空
while(n1 != null){
n2 = n1.next;
n1.next = n3;
n3 = n1;//n3前移
n1 = n2;
}
return res;
}
}
T236: 二叉树的最近公共祖先
传送门:O68-| 二叉搜索树的最近公共祖先、O68-||:二叉树的最近公共祖先
T238: 除自身以外数组的乘积
传送门:O66构建乘积数组
T239:滑动窗口最大值
传送门:O59
T240题目:搜索二维矩阵||
编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:
- 每行的元素从左到右升序排列。
- 每列的元素从上到下升序排列。
方法
- 根据矩阵行、列都排好序。有:矩阵中任何一个数A肯定比它左面和上面围成的区域中的数都大(即A必定大于以A为右下顶点的区域中的所有数)。
- 初始化起点A从矩阵右上角开始;目标值和A比较,若大于A,说明比第一行的所有数都大,A下移;若小于A,A左移,再比较;
- 循环执行2
手写过程

代码
class Solution {
public boolean searchMatrix(int[][] arr, int target) {
if(arr.length == 0){
return false;
}
int m = arr.length-1;
int n = arr[0].length-1;
int cur_m = 0;
int cur_n = n;
while(cur_m<= m && cur_n>=0){
if(target == arr[cur_m][cur_n]){//初始化起点A位于右上角
return true;
}else if(arr[cur_m][cur_n] < target){//大于A往下走
++cur_m;
}else{//小于A往左走
--cur_n;
}
}
return false;
}
}
T252: 用队列实现栈
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通队列的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
- void push(int x) 将元素 x 压入栈顶。
- int pop() 移除并返回栈顶元素。
- int top() 返回栈顶元素。
- boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
方法
- 用两个队列实现栈结构,即先进先出变为先进后出,其中这两个队列都是先进先出。
1、先把数存放在queue(先进先出),最后一个放的位于队列最左边
2、①取出最后一个数(pop)时,将queue中除最左边的数之外,全部放在help队列中,并按照先出先进原则
3、此时queue中只剩最左边的数,将这个数移除(队列中的poll方法)并把这个数返回给res
4、[重要]将help和queue的名字互换,即queue的数到help后,queue空了,help成为下一次的queue
5、②只是返回(不移除)栈顶的数时,和①一样,只是当queue只剩最后一个数时,赋给res
6、 同时将res添加到help中,即数据没有移除,只是全放在了help中,然后help再变成下一次的queue
手写过程

代码
class MyStack {
//用两个队列实现栈结构,即先进先出变为先进后出
//其中这两个队列都是先进先出
//先把数存放在queue(先进先出),最后一个放的位于队列最左边
//①取出最后一个数(pop)时,将queue中除最左边的数之外,全部放在help队列中,并按照先出先进原则
//此时queue中只剩最左边的数,将这个数移除(队列中的poll方法)并把这个数返回给res
//[重要]将help和queue的名字互换,即queue的数到help后,queue空了,help成为下一次的queue
//②只是返回(不移除)栈顶的数时,和①一样,只是当queue只剩最后一个数时,赋给res,
// 同时将res添加到help中,即数据没有移除,只是全放在了help中,然后help再变成下一次的queue
private Queue<Integer> queue = new LinkedList<>();
private Queue<Integer> help = new LinkedList<>();
/** Initialize your data structure here. */
public MyStack() {
}
/** Push element x onto stack. */
public void push(int x) {
queue.add(x);
}
/** Removes the element on top of the stack and returns that element. */
public int pop() {//移除栈顶的数,并返回这个数
if(queue.isEmpty()){
throw new RuntimeException("empty");
}
while(queue.size()>1){// =1时 这个数即是栈顶
help.add(queue.poll());
}
int res = queue.poll(); //把栈顶移除,并赋给res
swap(); //help和queue交换名字
return res;
}
/** Get the top element. */
public int top() { // 只是返回栈顶的数(不移除)
if(queue.isEmpty()){
throw new RuntimeException("empty");
}
while(queue.size()>1){// =1时 这个数即是栈顶
help.add(queue.poll());
}
int res = queue.poll();//把栈顶移除,并赋给res
help.add(res); //重新把res添加到help中
swap();//help 成为queue
return res;
}
/** Returns whether the stack is empty. */
public boolean empty() {
return help.size() == 0 && queue.size() ==0;
}
public void swap(){
Queue<Integer> temp = help;
help = queue;
queue = temp;
}
}
/**
* Your MyStack object will be instantiated and called as such:
* MyStack obj = new MyStack();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.top();
* boolean param_4 = obj.empty();
*/
T264:丑数||
-
题目

-
思路(三指针)

-
代码
class Solution {
public int nthUglyNumber(int n) {
int[] dp = new int[n];//dp[i],存放第i个丑数
int index1=0;
int index2=0;
int index3=0;
dp[0] = 1;//第一个丑数是1;
for(int i=1;i<n;i++){
dp[i] = Math.min(Math.min(dp[index1]*2,dp[index2]*3),dp[index3]*5);
//取出当前三个因子序列指针对应元素的最小值
if(dp[i] == dp[index1]*2) index1++;//"2"因子序列元素指针后移
if(dp[i] == dp[index2]*3) index2++;//"3"因子序列元素指针后移
if(dp[i] == dp[index3]*5) index3++;//"5"因子序列元素指针后移
}
return dp[n-1];
}
}
T279:完全平方数
题目:

思路:动态规划
- dp[i]:表示完全平方数和为i的 【最小】组合【个数】
- 初始状态dp[i]均取最大值i,即 1+1+…+1,共i个1; dp[0] = 0
- 任何一个完全平方数x=a×a的的最小组合个数是1,即dp[x]=dp[a×a]=1,因为它本身就是完全平方数,用它自己就可以完成表示,且是最小的组合个数。例如:x=16,它的组合可以是{111,1}、{4}、{2,2,2,2},但其中最小的组合个数是1,即dp[4*4]=1
代码:
class Solution {
public int numSquares(int n) {
int[] dp = new int[n+1];//默认初始化为0
for(int i=1;i<=n;i++){
dp[i]=i;//最坏情况,即都是1构成的,共i个1
for(int j=1;i-j*j>=0;j++){
dp[i] = Math.min(dp[i],dp[i-j*j]+1);
//dp[i] = Math.min(dp[i],dp[i-j*j]+dp[j*j]);其中j*j是完全平方数,最小组合数是1,即dp[j*j]=1
}
}
return dp[n];
}
}
T283:移动零
题目:

思路:双指针

j 指针:指向左边非零元素末尾,即待放的0的位置
i 指针:i指向当前数
当前数nums[i]!=0,放到 j 所指的地方
代码:
class Solution {
public void moveZeroes(int[] nums) {
int j=0;//指向非零元素末尾,即待放的0的位置
for(int i=0;i<nums.length;i++){
//i指向当前数
if(nums[i] != 0){//当前数!=0,放到j所指的地方
swap(nums,i,j);
j++;
}
}
}
public void swap(int[] nums,int a ,int b){
int tmp = nums[b];
nums[b] = nums[a];
nums[a]=tmp;
}
}
T287: 寻找重复数
题目:

思路:双指针+环形链表
类似 T142:环形链表
- 根据数组和索引的映射关系构建环形链表

代码:
class Solution {
public int findDuplicate(int[] nums) {
int slow =0;
int fast=0;
slow = nums[slow];//初始化指针位置,第一个位置
fast = nums[nums[fast]];//初始化为第二给位置
//用映射再映射的关系来模拟走两步;因为映射一次相当于走一步,再映射一次就再走一步
while(slow != fast){//第一次相遇
slow = nums[slow];
fast = nums[nums[fast]];
}
fast =0;//快指着回到起点和slow同时走
while(slow != fast){//再一次相遇
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
}
T297:二叉树的序列化与反序列化
传送门:O37
T300: 最长上升子序列
题目:
思路:动态规划
dp[i]:以nums[i]结尾的最长子序列长度
在每个dp[i]中遍历j,j∈0~i,

例如下面数组中‘21’的dp[6].即以21结尾的上升子序列

代码:
class Solution {
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
int res=0;
Arrays.fill(dp,1);
for(int i=0;i<nums.length;i++){
for(int j=0;j<i;j++){//遍历当前nums[i]之前可以和nums[i]组成上升序列的序列
if(nums[j]<nums[i]) dp[i]=Math.max(dp[i],dp[j]+1);//记录以nums[i]结尾的上升子序列最大值
}
res = Math.max(res,dp[i]);
}
return res;
}
}
T301:删除无效的括号
题目:

思路:回溯(DFS)

代码:
class Solution {
// 用集合存储所有正确的字符串,可避免重复
private Set<String> set = new HashSet<>();
public List<String> removeInvalidParentheses(String s) {
char[] ss = s.toCharArray();
int open = 0, close = 0;
// 获取应该去除的左右括号数
for (char c : ss) {
if (c == '(') open ++;
else if (c == ')')
if (open > 0) open --;
else close ++;
}
// 回溯
backTracking(ss, new StringBuilder(), 0, 0, 0, open, close);
return new ArrayList(set);
}
public void backTracking(char[] ss, StringBuilder sb, int index, int open, int close, int openRem, int closeRem) {
/**
* 回溯函数
* 分别对字符串中的每一位置的字符进行处理,最终获得符合要求的字符串加入集合中
* @param ss 字符串对应的字符数组
* @param sb 储存当前处理过且未去除字符的字符串
* @param index 当前处理的字符位置
* @param open 当前sb中储存的左括号数
* @param close 当前sb中储存的右括号数
* @param openRem 当前需要去除的左括号数
* @param closeRem 当前需要去除的右括号数
*/
// 所有字符都处理完毕
if (index == ss.length) {
// 如果应去除的左右括号数都变为0,则将sb插入set
if (openRem == 0 && closeRem == 0)
set.add(sb.toString());
return;
}
// 去掉当前位置的字符(括号),并处理下一个字符
if (ss[index] == '(' && openRem > 0 || ss[index] == ')' && closeRem > 0)
backTracking(ss, sb, index + 1, open, close, openRem - (ss[index] == '(' ? 1 : 0), closeRem - (ss[index] == ')' ? 1 : 0));
// 不去掉当前位置字符
// 将当前位置字符插入sb
sb.append(ss[index]);
// 当前位置不为括号,则直接处理下一个字符
if (ss[index] != '(' && ss[index] != ')')
backTracking(ss, sb, index + 1, open, close, openRem, closeRem);
// 当前位置为左括号,增加左括号计数,处理下一个字符
else if (ss[index] == '(')
backTracking(ss, sb, index + 1, open + 1, close, openRem, closeRem);
// 当前位置为右括号,且当前左括号计数大于右括号计数,则增加右括号计数,处理下一个字符
else if (open > close)
backTracking(ss, sb, index + 1, open, close + 1, openRem, closeRem);
// 去除当前加入sb的字符
sb.deleteCharAt(sb.length() - 1);
}
}
T309:最佳买卖股票时机含冷冻期
题目:

思路:动态规划



代码:
class Solution {
public int maxProfit(int[] prices) {
int n =prices.length;
int[][] dp = new int[n][3];
dp[0][0] = -prices[0];//初始化
for(int i=1;i<n;i++){
dp[i][0] = Math.max(dp[i-1][0],dp[i-1][2]-prices[i]);
//1、第i天结束后仍然有票
dp[i][1] = dp[i-1][0]+prices[i];
//2、第i天结束后没有票了,且处于冻结期
dp[i][2] = Math.max(dp[i-1][2],dp[i-1][1]);
//3、第i天结束后没有票了,且不处于冻结期
}
return Math.max(dp[n-1][1],dp[n-1][2]);//选择上述3情况的最大值
//因为情况1的最后一天表示:最后一天结束后还有票,这是没有意义的,所以不考虑它
}
}
T123:买卖股票的最佳时机|||




T312: 戳气球
题目:

思路:动态规划





代码:
class Solution {
public int maxCoins(int[] nums) {
int n= nums.length;
int[] val = new int[n+2];
val[0]=1;val[n+1]=1;
int[][] dp = new int[n+2][n+2];
for(int i=1;i<=n;i++){
val[i] = nums[i-1];
}
for(int i=n-1;i>=0;i--){//
for(int j=i+2;j<=n+1;j++){
for(int k=i+1;k<j;k++){
//最后戳破的是k
//先解决i到k和k到j的子问题,最后剩下val[k]和左右的val[i],val[j]。
dp[i][j] = Math.max(dp[i][j],dp[i][k]+dp[k][j]+val[i]*val[k]*val[j]);
}
}
}
return dp[0][n+1];
}
}
T322: 零钱兑换
题目:

思路:动态规划



代码:
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount+1];
Arrays.fill(dp,amount+1);
//最多情况,amount数量个1元硬币,但是可能给的硬币不包含1元;amount+1用于初始化,是不可能的状态
dp[0]=0;//当有0个硬币时,需要的数量是0;
for(int i=1;i<=amount;i++){
for(int j=0;j<coins.length;j++){
if(coins[j]<=i){
dp[i] = Math.min(dp[i],dp[i-coins[j]]+1);
}
}
}
return dp[amount] > amount? -1 : dp[amount];
}
}
T338:比特位计数
题目:

思路:动态规划

代码:
class Solution {
public int[] countBits(int n) {
int[] dp = new int[n+1];
dp[0] = 0;
for(int i=1;i<=n;i++){
if((i&1)==1){//奇数
dp[i] = dp[i-1]+1;
}else{//偶数
dp[i] = dp[i>>1];
}
}
return dp;
}
}
T347: 前k个高频元素
题目:

思路:哈希表+堆

代码:
class Solution {
public int[] topKFrequent(int[] nums, int k) {
HashMap<Integer,Integer> map = new HashMap<>();
for(int num: nums){//map key存数,value存该数出现的次数
map.put(num,map.getOrDefault(num,0)+1);
}
PriorityQueue<Integer> queue = new PriorityQueue<>( (a,b)-> map.get(a)-map.get(b) );
//queue根据数的次数的顺序来存放数
for(int key :map.keySet()){//map.keySet()返回key对应的集合
//是先存放,再判断
queue.offer(key);//queue只存放key,即数
if(queue.size()>k) queue.poll();
}
int[] res = new int[k];
for(int i=0;i<k;i++){
res[i]=queue.poll();
}
return res;
}
}
T394: 字符串解码
题目:

思路:辅助栈

代码:
class Solution {
public String decodeString(String s) {
// 思路: 乘法和 递推公式 前一个和作为下一个加法的加数
// 3[a]2[bc] = 3a+2bc = (3a + "") + 2bc
// 3[a2[c]] = 3(a + 2c) = 3(a + 2c) + ""
// 1. 初始化倍数和res 及其对应栈
int multi = 0;
StringBuilder res = new StringBuilder();
Deque<Integer> multi_stack = new LinkedList<>();//存储倍数
Deque<String> res_stack = new LinkedList<>();//存储字符串
// 2. 遍历字符
char[] chars = s.toCharArray();
for (char ch : chars) {
// 3. 统计倍数
if (ch >= '0' && ch <= '9')
multi = multi * 10 + (ch - '0');//针对连续数字情况,例如连着两个ch都是数字,23 ,multi=2*10+3
// 4. 统计res
else if (ch >= 'a' && ch <= 'z')
res.append(ch);
// 5. 入栈并重置临时变量
else if (ch == '[') {
multi_stack.push(multi);
res_stack.push(res.toString());
// 重置开始下一轮重新统计
multi = 0;
res = new StringBuilder();
// 6. 出栈做字符串乘法和加法
} else {
int cur_multi = multi_stack.pop();
StringBuilder temp = new StringBuilder();
for (int i = 0; i < cur_multi; i++)
temp.append(res);// 乘以当前统计字符串res,即累计追加cur_multi次
// 加上前一个统计字符串作为当前res
res = new StringBuilder(res_stack.pop() + temp);
}
}
return res.toString();
}
}
?T399:除法求值
题目:

思路:带权并查集



合并?

T402: 根据身高重建队列
题目:

思路:
先对输入数组排序,h降序,k升序

规则:
- 新数组res当前长度i > 当前需要发的people元素p[i] 的ki时,把p[i]插入到ki位置
- 新数组res当前长度i <= 当前需要发的people元素p[i] 的ki时, 追加到res末尾
举例:


代码:
class Solution {
public int[][] reconstructQueue(int[][] people) {
//按数组第一个元素进行降序,按第二个元素进行升序
Arrays.sort(people, new Comparator<int[]>() {
@Override
public int compare(int[] person1, int[] person2){
if (person1[0] != person2[0]) return person2[0] - person1[0];
else return person1[1] - person2[1];
}
});
//新建一个list,用于保存结果集
List<int[]> list = new LinkedList<>();
for (int i = 0; i < people.length; i++) {
if (list.size() > people[i][1]){
//结果集中元素个数大于第i个人前面应有的人数时,将第i个人插入到结果集的 people[i][1]位置
list.add(people[i][1],people[i]);
}else{
//结果集中元素个数小于等于第i个人前面应有的人数时,将第i个人追加到结果集的后面
list.add(list.size(),people[i]);
}
}
//将list转化为数组,然后返回
return list.toArray(new int[list.size()][]);
}
}
T416: 分割等和子集
题目:

思路:背包问题,动态规划
dp[i][j] 表示从数组的 [0,i]下标范围内选取若干个正整数(可以是 0 个),是否存在一种选取方案使得被选取的正整数的和等于 j



代码:
class Solution {
public boolean canPartition(int[] nums) {
int n = nums.length;
if (n < 2) {
return false;
}
int sum = 0, maxNum = 0;
for (int num : nums) {
sum += num;
maxNum = Math.max(maxNum, num);
}
if (sum % 2 != 0) {
return false;
}
int target = sum / 2;
if (maxNum > target) {
return false;
}
boolean[][] dp = new boolean[n][target + 1];
for (int i = 0; i < n; i++) {
dp[i][0] = true;
}
dp[0][nums[0]] = true;
for (int i = 1; i < n; i++) {
int num = nums[i];
for (int j = 1; j <= target; j++) {
if (j >= num) {
dp[i][j] = dp[i - 1][j] | dp[i - 1][j - num];
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[n - 1][target];
}
}
T435: 无重叠区间


class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
int count=0;//记录重叠区间的个数
if(intervals.length==0) return 0;
Arrays.sort(intervals,new Comparator<int[]>(){
public int compare(int[] o1,int[] o2){
return o1[1] - o2[1];
}
});
int rightEnd = intervals[0][1];//排序后首个区间的右端点
for(int i =1;i<intervals.length;i++){
if(intervals[i][0]>=rightEnd){//说明没有重叠,更新区间
rightEnd = intervals[i][1];
}else count++;
}
return count;
}
}
T437: 路径总和 III
传送门:
1、T112路径总和 |

2、T113路径总和 || = O34二叉树中和为每一 值的路径
题目:

思路:前缀和





代码:
class Solution {
Map<Integer,Integer> mem = new HashMap<Integer,Integer>();//保存前缀树
int target;
public int pathSum(TreeNode root, int targetSum) {
target = targetSum;
mem.put(0,1);//前缀树为0的个数至少是一个
return dfs(root,0);
}
public int dfs(TreeNode root,int curSum){
if(root == null) return 0;
curSum += root.val;//得到当前前缀树的值
int res = 0;
res = mem.getOrDefault(curSum-target,0);//得到我们想要前缀树的个数,想要前缀树值就是当前前缀树值减去目标值
mem.put(curSum,mem.getOrDefault(curSum,0)+1);//将当前前缀树的值保存
int left = dfs(root.left,curSum);//遍历左边
int right = dfs(root.right,curSum);//遍历右边
mem.put(curSum,mem.get(curSum)-1);//防止左边前缀树影响右边前缀树,左边前缀树可能有个为6,右边正好想要一个前缀树为6的,这样子就出错了
return res+left+right;//结果是当前节点前缀树的个数加上左边满足的个数加右边满足的个数
}
}
T438:找到字符串中所有字母异位词
题目:

思路:

计算p的每个字母出现的次数,记录在26个长度的数组中;
然后以p的长度作为滑动窗口的长度,对s进行窗口滑动,每次判断窗口内的字母频次是否等于p的频次,如果相等,则添加当前窗口的索引i-m+1
其中每次滑动窗口时需要把挪出的字母对应的频次-1,挪入的字母频次+1
代码:
class Solution {
public List<Integer> findAnagrams(String s, String p) {
int slen = s.length();
int plen = p.length();
List<Integer> res = new ArrayList<>();
int[] scn = new int[26];//记录每个字母出现的次数
int[] pcn = new int[26];
if(slen<plen) return res;
//记录p每个字母出现的次数,记录s的第一个窗口中个字母的次数(p长度即窗口长度)
for(int i=0;i<plen;i++){
pcn[p.charAt(i)-'a']++;
scn[s.charAt(i)-'a']++;
}
if(Arrays.equals(pcn,scn)) res.add(0);//s第一个窗口和p相同,返回第一个窗口的索引0
for(int i=plen;i<slen;i++){//从s的第二个窗口开始判断,再判断第三个窗口、、、、、
scn[s.charAt(i-plen)-'a']--;//每次挪动窗口后把左边刚挪出的字母的频次减1
scn[s.charAt(i)-'a']++;//每次挪动窗口后把右边刚挪入的字母的频次加1
if(Arrays.equals(scn,pcn)){//判断当前s的窗口内的数组值和p的数组是否相等,即判断窗口内的频次是否和p的频次一致
res.add(i-plen+1);//一致则添加当前窗口的起点位置,1~(slen-plen+1),即第二个窗口到最后一个窗口的索引
}
}
return res;
}
}
T448:找到所有数组中消失的数字
题目:
思路:原地修改数组
例如[7\8\5\1\6\3\2]中nums[3]
对应的是1,按理说,1应该放在索引0位置上,即nums[3]-1=0; 把索引位置用负数表示,代表出现过


如图,
4应该放在索引3上,索引3上的数乘-1,表示4出现过
3应该放在索引2上,索引2上的数乘-1,表示3出现过
2应该放在索引1上,索引1上的数乘-1,表示2出现过
7应该放在索引6上,索引6上的数乘-1,表示7出现过
8应该放在索引7上,索引7上的数乘-1,表示8出现过
2应该放在索引1上,索引1上的数乘-1,表示2出现过
3应该放在索引2上,索引2上的数乘-1,表示3出现过
1应该放在索引0上,索引0上的数乘-1,表示1出现过
最后只有位置4、5上的数是正数,即数字5、6没出现
代码:
class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
int n = nums.length;
LinkedList<Integer> list = new LinkedList<>();
for(int i=0;i<n;i++){
if(nums[Math.abs(nums[i])-1]>0){
nums[Math.abs(nums[i])-1] *=-1;
//将nums[i]这个数 应该放在索引nums[i]-1位置上,*-1表示出现了
}
}
for(int i=0;i<n;i++){
if(nums[i]>0){
list.add(i+1);
}
}
return list;
}
}
T461:汉明距离
题目:

思路:位运算

1、内置位计数方法

2、移位实现位计数

class Solution {
public int hammingDistance(int x, int y) {
int s = x ^ y, ret = 0;
while (s != 0) {
ret += s & 1;
s >>= 1;
}
return ret;
}
}
T494: 目标和
类似T416分割等和子集
题目:

思路:背包问题,动态规划
问题:原意需要求‘+’和‘-’的摆放位置组合数,使得目标和是target
转换为:先求出数组总和sum,假设‘-’号元素和是neg,那么sum-neg就表示‘+’数的和,并且有targrt=正数和-负数和=(sum-neg)-neg=sum-2neg,于是有neg=(sum-target)/2, 因此我们只需求解数组中负数的和
即求解数组n个数组和为neg的方案


dp[ i ][ j ]:在数组 nums 的前 i个数中选取元素,使得这些元素之和等于 j 的方案数
和T416原理一致
代码:
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int n = nums.length;
int sum=0;
for(int num :nums){//求总和
sum += num;
}
int diff = sum-target;
if(diff<0 || diff%2!=0) return 0;
int neg = diff/2;//需要放符号的那些数的和
int[][] dp = new int[n+1][neg+1];
dp[0][0]=1;
for(int i=1;i<=n;i++){
int num=nums[i-1];//i从1开始,所有-1
for(int j=0;j<=neg;j++){
if(j>=num){//注意这里有等号
dp[i][j]=dp[i-1][j]+dp[i-1][j-num];//不选num + 选num
}else{
dp[i][j]=dp[i-1][j];//不能选num
}
}
}
return dp[n][neg];
}
}
T498题目:对角线遍历(之字形打印数组)
给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

方法
1)、斜线打印,定义A、B两点,初始均在(0,0),A点向右移,B点向下移,且A、B同步移动
2)、A点移动到右边界时,就开始向下移动;B点移动到下边界时就向右移动
手写过程

代码
/**
* 斜线打印,定义A、B两点,初始均在(0,0),A点向右移,B点向下移,且A、B同步移动
* A点移动到右边界时,就开始向下移动;B点移动到下边界时就向右移动
* @param A_r A行数
* @param A_c A列数
* @param B_r B行数
* @param B_c B列数
* @param flag 判断打印方向,false则从左下至右上打印。反之。。。
*/
public int[] findDiagonalOrder(int[][] arr) {
if(arr == null || arr.length ==0){
return new int[]{};
}
int end_R = arr.length-1;
int end_C = arr[0].length-1;
int A_r = 0;
int A_c = 0;
int B_r = 0;
int B_c = 0;
int[] res = new int[(end_R+1)*(end_C+1)];
boolean flag = false;
int i=0;
while(A_r <= end_R){ //外层循环,即多少条斜线
//内层循环开始。
int cur_Ar = A_r;
int cur_Ac = A_c;
int cur_Br = B_r;
int cur_Bc = B_c;
if(flag){//从右上至左下
while(cur_Ar <= B_r){
res[i++] = arr[cur_Ar++][cur_Ac--];
}
}else{//从左下至右上打印
while(cur_Br >= A_r){
res[i++] = arr[cur_Br--][cur_Bc++];
}
}
//内层循环结束。
//判断A、B是否到达边界
// A_r 开始不变,A_c走的最后, 开始增加
A_r = A_c == end_C ? A_r+1 : A_r; //若A到边界,下移,
A_c = A_c == end_C ? A_c : A_c+1; //即 行+1,列不变
// B_c 开始不变,B_r走的最后, 开始增加
B_c = B_r == end_R ? B_c+1 : B_c; //若B到边界,右移,
B_r = B_r == end_R ? B_r : B_r+1; //即 列+1,行不变
// [切记]B_c和B_r的顺序不能颠倒!!否则出错
flag = !flag; //每外层循环一次,方向改变一次
}
return res;
}
T538:把二叉搜索树转换为累加数
题目:


思路:反序中序遍历
本题中要求我们将每个节点的值修改为原来的节点值加上所有大于它的节点值之和。这样我们只需要反序中序遍历该二叉搜索树,记录过程中的节点值之和,并不断更新当前遍历到的节点的节点值,即可得到题目要求的累加树。
代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int sum=0;
public TreeNode convertBST(TreeNode root) {
if(root != null){
convertBST(root.right);
sum += root.val;
root.val = sum;
convertBST(root.left);
}
return root;
}
}
T543: 二叉树的直径
题目:

思路:
我们知道一条路径的长度为该路径经过的节点数减一,所以求直径(即求路径长度的最大值)等效于求路径经过节点数的最大值减一。

代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int res;
public int diameterOfBinaryTree(TreeNode root) {
depth(root);
return res;
}
public int depth(TreeNode node){
if(node == null) return 0;
int left = depth(node.left);
int right = depth(node.right);
res = Math.max(res,left+right);
//将每个节点最大直径(左子树深度+右子树深度)与当前最大值比较并取大者
return Math.max(left,right)+1;//返回当前节点的最大深度
}
}
T560:和为K的子数组
题目:

思路:前缀和+哈希表
1、暴力法
class Solution {
public int subarraySum(int[] nums, int k) {
int count=0;
int n = nums.length;
for(int i=0;i<n;i++){
int sum=0;
for(int j=i;j<n;j++){
sum += nums[j];
if(sum==k) count++;
}
}
return count;
}
}
2、前缀和
class Solution {
public int subarraySum(int[] nums, int k) {
int count=0;
int n = nums.length;
int[] presum = new int[n+1];//前缀和数组,存放以i结尾的前缀和
presum[0] = 0;
for(int i=0;i<n;i++){
presum[i+1] = presum[i] + nums[i];
}
for(int i=0;i<n;i++){
for(int j=i;j<n;j++){
if(presum[j+1]-presum[i]==k) count++;
}
}
return count;
}
}
3、前缀和+哈希表




class Solution {
public int subarraySum(int[] nums, int k) {
int n = nums.length;
HashMap<Integer,Integer> map = new HashMap<>();
map.put(0,1);//初始化是1!!
int preSum=0;
int count=0;
for(int i=0;i<n;i++){
preSum +=nums[i];
if(map.containsKey(preSum - k)){
count += map.get(preSum - k);
}
map.put(preSum,map.getOrDefault(preSum,0)+1);
}
return count;
}
}
T581:最短无序连续子数组
题目:

思路:

首先找到局部最大值和局部最小值,然后根据局部最值和局部之外的点比较从而确定最短子数组的区间端点。
- 除了子数组之外其余元素是升序排列的,当出现nums[i-1]<nums[i]时,说明进入到子数组了,然后在子数组里找到最小值,即子数组里所有降序的片段中的最小值;反之找到局部最大值

代码:
class Solution {
public int findUnsortedSubarray(int[] nums) {
int n = nums.length;
int max = Integer.MIN_VALUE; int min = Integer.MAX_VALUE;
//从左到右找到局部最小值
for(int i=1;i<n;i++){//因为从nums[i-1]开始
if(nums[i]<nums[i-1]){//第一次出现,说明已经进入子数组了
min = Math.min(min,nums[i]);
//然后记录在子数组里所有的降序片段右端点,最小的那个即局部最小值
}
}
//从右到左找到局部最大值
for(int i= n-1;i>=1;i--){
if(nums[i-1]>nums[i]){//第一次出现,说明已经进入子数组了
max = Math.max(max,nums[i-1]);
//然后记录在子数组里所有的升序(从右往左看升序)片段左端点,最大的那个即局部最大值
}
}
int left;//找到子数组区间端点
int right;
for(left=0;left<n;left++){
if(nums[left]>min) break;
}
for(right=n-1;right>0;right--){
if(nums[right]<max) break;
}
return right-left<0? 0 : right-left+1;
}
}
优化:可以把找最大值最小值放在一个循环里
for(int i=1;i<n;i++){
if(nums[i]<nums[i-1]){
min = Math.min(min,nums[i]);
max = Math.max(max,nums[i-1]);
}
}
T617: 合并二叉树
题目:

思路:dfs

代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
if(root1 == null) return root2;
if(root2 == null) return root1;
TreeNode newNode = new TreeNode(root1.val + root2.val);
newNode.left = mergeTrees(root1.left,root2.left);
newNode.right = mergeTrees(root1.right,root2.right);
return newNode;
}
}
T621:任务调度器
题目:


思路:填桶




代码:
class Solution {
public int leastInterval(char[] tasks, int n) {
int[] buckets = new int[26];
//统计每个任务出现的次数
for(char task : tasks){
buckets[task - 'A'] +=1;
}
int max=0;//计算出现次数最多的任务
for(int i=0;i<26;i++){
max = Math.max(buckets[i],max);
}
//计算maxCount
int maxCount=0;//这个是0,而不是初始化为1
for(int i=0;i<26;i++){//因为遍历时把26个都遍历了,包括最大任务,所以最后maxCount必>=1;
if(buckets[i]==max){
maxCount++;
}
}
int res=0;
res = Math.max( (max-1)*(n+1)+maxCount, tasks.length);
return res;
}
}
T647: 回文子串
同类型:T5最长回文子串
题目:

思路:中心扩展

枚举每一个可能的回文中心,然后用两个指针分别向左右两边拓展,当两个指针指向的元素相同的时候就拓展,否则停止拓展。
代码:
class Solution {
public int countSubstrings(String s) {
int n = s.length();
int res =0;
//以一个点为扩展中心,共有n个中心
for(int center =0;center<n;center++){
int left = center;
int right = center;
while(left>=0 && right<n && s.charAt(left)==s.charAt(right)){
res++;
left--;
right++;
}
}
//以两个点为扩展中心,共有n-1个中心
for(int center =0;center<n-1;center++){
int left = center;
int right = center+1;
while(left>=0 && right<n && s.charAt(left)==s.charAt(right)){
res++;
left--;
right++;
}
}
return res;
}
}
T739:每日温度(单调栈)
单调栈:判别是否需要使用单调栈,如果需要找到左边或者右边第一个比当前位置的数大或者小,则可以考虑使用单调栈;eg:接雨水、矩形面积
题目:

思路:单调栈
维护一个存储【下标】的单调栈,从栈底到栈顶的下标对应的温度列表中的温度依次递减。如果一个下标在单调栈里,则表示尚未找到下一次温度更高的下标。

代码:
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int[] res = new int[temperatures.length];
Stack<Integer> stack = new Stack<>();
for(int i=0;i<temperatures.length;i++){
while(!stack.isEmpty() && temperatures[i]>temperatures[stack.peek()]){
int pre = stack.pop();//栈顶元素代表的索引
res[pre] = i-pre;//索引差
}
stack.add(i);
}
return res;
}
}
T783:二叉搜索树节点最小距离
题目:

思路:

代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
//二叉搜索数的中序遍历是有序的,因此按照中序遍历,每次比较当前节点和上一节点的差值
class Solution {
private TreeNode pre_node = null;//定义先前节点
private int min = Integer.MAX_VALUE;
public int minDiffInBST(TreeNode root) {
inOrder(root);
return min;
}
public void inOrder(TreeNode node){
if(node == null){ return;}
inOrder(node.left);
if(pre_node!=null){ min = Math.min(node.val-pre_node.val,min);}
//如果(pre_node=null 表示根节点没有先前节点,故直接跳过
pre_node = node;
inOrder(node.right);
}
}
T876: 找链表中间节点
注意链表长度为偶数时,返回第 2 个结点的细节;
重点:
1、考虑fast指针起点,是head还是head.next
2、考虑while循环条件,是fast!=null&&fast.next!=null 还是fast.next!=null&&fast.next.next!=null

T1094:拼车


T1248:统计优美子数组(前缀和)


T1893: 检查是否区域内所有整数都被覆盖






本文精选LeetCode经典题目,涵盖数组、链表、字符串、树等多种数据结构与算法,如两数之和、合并两个有序链表、括号生成等,提供详细解题思路与代码实现。
2885

被折叠的 条评论
为什么被折叠?



