文章目录
- ·数组array
- ·链表
- ·哈希表
- ·字符串
- ·双指针法
- ·栈与队列
- ·二叉树
- ·回溯算法
- ·贪心算法
- ·动态规划
- 五部曲
- 509 斐波那契数
- 70 爬楼梯
- 746 花费最小爬楼梯
- 62 不同路径
- 63 不同路径
- 343 整数拆分
- 96 不同的二叉树
- 416 分割等和子集
- 1049 最后一块石头的重量
- 494 目标和
- 474 一和零
- 518 零钱兑换
- 377 组合数IV
- 70 爬楼梯
- 322 零钱兑换II
- 279 完全平方数
- 139 单词拆分
- 198 打家劫舍
- 213 打家劫舍II
- 337 打家劫舍III
- 121 买卖股票的最佳时机
- 122 买卖股票的最佳时机II
- 123 买卖股票的最佳时机III
- 188 买卖股票的最佳时机IV
- 309 买卖股票的最佳时机含冷冻期
- 714 买卖股票的最佳时机含手续费
- 300 最长递增子序列
- 674 最长连续递增子序列
- 718 最长重复子数组--连续
- 1143 最长公共子序列--不连续
- 1035 不相交的线
- 53 最大子数组和
- 392 判断子序列
- 115 不同的子序列
- 583 两个字符串的删除操作
- 72 编辑距离
- 647 回文子串
- 516 最长回文子序列
·数组array
1、理论知识
【声明数组】
·int[] a = new int[100] a.length = 100
·int[] a = new int[n]
·int[] a = {1,2,3,4}
·允许结果长度为0的数组:new int[0] new int[]{}
·对象数组初始化为null String[] str = new String[100],初始化为null; int[]初始化为0,boolean[]初始化为false
【常用API】
Arrays·toString(a)
Arrays·sort(a)
Arrays·copyOf(a)
Arrays·copyOf(a,start,end)
Arrays·binarySearch(a)
Arrays·binarySearch(a,start,end)
Arrays·fill(a,value)
Arrays.equals(a,b)
【二维数组】:数组的数组(Java内部实际不存在二维数组)
【常用API】
Arrays.deepToString(a)
2、二分查找
704 二分查找
【关键词】有序的数组 无重复元素(确保目标位置只有一个)
【注意】 target下标在[left,right]和[left,right)代码是不一样的,或者说right更新时不同 区别在于while和right的更新
【题目】 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存 在返回下标,否则返回 -1。
【示例】
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
class Solution {
public int search(int[] nums, int target) {
int left = 0,right = nums.length-1;
while(left <= right){//[left,right]
int mid = left + (right - left)/2;//防止(left+right)/2溢出
if(nums[mid] < target){
left = mid + 1;
}else if(nums[mid] > target){
right = mid - 1;//[left,right]
}else{
return mid;
}
}
return -1;
}
}
162 寻找峰值
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ayYK1D1f-1649300405772)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220214220432300.png)]
【示例】
输入:nums = [1,2,3,1]
输出:2
解释:3 是峰值元素,你的函数应该返回其索引 2。
【解答】
/**
爬坡法
*/
class Solution {
public int findPeakElement(int[] nums) {
int left = 0,right = nums.length-1;//[]
while(left<right){//不能是等于 否则陷入循环
int mid = left + (right-left)/2;
if(nums[mid]>nums[mid+1]){//左侧存在峰值
right = mid;
}else{//相邻元素不存在相同的值
left = mid+1;
}
}
return left;
}
}
public int findPeakElement (int[] nums) {
// write code here
int left = 0,right = nums.length - 1;
while(left <= right){
//System.out.println(left +" "+right);
if(left == right){
return left;
}
int mid = ((right - left)>>1)+left;//mid>=left
if(nums[mid]<nums[mid+1]){//上升 注意是跟mid+1比较
left = mid + 1;
}else{//下坡
right = mid;
}
}
return left;
}
35 搜索插入位置
【题目】 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插 入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
·示例1
输入: nums = [1,3,5,6], target = 5
输出: 2
·示例2
输入: nums = [1,3,5,6], target = 2
输出: 1
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0,right = nums.length-1;
while(left <= right){
int mid = left + (right - left)/2;
if(nums[mid] < target){
left = mid + 1;
}else if(nums[mid] > target){
right = mid - 1;
}else{
return mid;
}
}//跳出循环时 left > right
return left;//如示例2 left = 1 right =0
}
}
34 在排序数组中查找元素的第一个和最后一个位置
题目
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
示例1
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例2
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
class Solution {
public int[] searchRange(int[] nums, int target) {
if(nums.length == 0){
return new int[]{-1,-1};
}
int start = -1,end = -1;
start = findFirstLocation(nums,target);//第一次出现的位置
if(start != -1){
end = findLastLocation(nums,target);
}
return new int[]{start,end};
}
public int findFirstLocation(int[] nums,int target){//最后一次出现的位置
int left = 0,right = nums.length-1;
while(left < right){//[left,right] 不同于只出现一次元素的位置
int mid = left + (right - left)/2;
if(nums[mid] < target){
left = mid + 1;
}else if(nums[mid] == target){
right = mid;
}else{
right = mid - 1;
}
}
if(nums[left] == target){
return left;
}else{
return -1;
}
}
public int findLastLocation(int[] nums,int target){
int left = 0,right = nums.length-1;
while(left < right){//[left,right] 不同于只出现一次元素的位置
int mid = left + (right - left + 1)/2;//元素的上界 可以手动演示一下理解
if(nums[mid] < target){
left = mid + 1;
}else if(nums[mid] == target){
left = mid;
}else{
right = mid - 1;
}
}
//不用判断nums[left] == target? 因为若有start 必有end
return left;
}
}
69 x的算数平方根
【题目】
给你一个非负整数 x ,计算并返回 x 的算术平方根 。由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
【示例】
输入:x = 4
输出:2
输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
法1
class Solution {
public int mySqrt(int x) {
int left = 1,right = x;//left = 0开始判断比较好 题目中x是非负整数
while(left <= right){
int mid = left + (right - left)/2;
if((long)mid*mid > x){//若是int 则会溢出,需要转成long
right = mid - 1;
}else if((long)mid*mid == x){
return mid;
}else{
left = mid + 1;
}
}
//left > right
return right;
}
}
【注】会溢出,故需要转成long
【法2】
class Solution {
public int mySqrt(int x) {
int left = 0,right = x;//left = 1也可 需要单独判断 x= 0
int res = -1;//记录k*k<=x的值 保留最大值
while(left <= right){
int mid = left + (right - left)/2;
if((long)mid*mid <= x){
res = mid;
left = mid + 1;
}else{
right = mid - 1;
}
}
return res;
}
}
367 有效的完全平方数
【题目】
给定一个 正整数 num ,编写一个函数,如果 num 是一个完全平方数,则返回 true ,否则返回 false 。
进阶:不要 使用任何内置的库函数,如 sqrt 。
示例1
输入:num = 16
输出:true
示例2
输入:num = 14
输出:false
class Solution {
public boolean isPerfectSquare(int num) {
int left = 1,right = num;
while(left <= right){
int mid = left + (right - left)/2;
if((long)mid*mid == num){
return true;
}else if((long)mid*mid < num){
left = mid + 1;
}else{
right = mid - 1;
}
}
return false;
}
}
3、移除元素–双指针
【注意】 双指针法:使用快慢指针 慢指针指向待保存的元素的下标 快指针遍历整个数组.多用于要求O(n)时间复杂度的。
用一个for循环完成两个for循环的工作量
27 移除元素
【题目】
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素
【示例】
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。
class Solution {
public int removeElement(int[] nums, int val) {
int slowIndex = 0;//记录不等于val的数组下标
for(int fastIndex = 0;fastIndex<nums.length;fastIndex++){
if(nums[fastIndex] != val){
nums[slowIndex] = nums[fastIndex];
slowIndex++;//下次使用
}
}
return slowIndex;
}
}
26 删除有序数组中的重复项
【题目】
给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
【示例】
输入:nums = [1,1,2]
输出:2, nums = [1,2]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素
输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。
class Solution {
public int removeDuplicates(int[] nums) {
if(nums.length ==0 || nums.length == 1){
return nums.length;
}
int slowIndex = 0;
for(int fastIndex = 1;fastIndex<nums.length;fastIndex++){
if(nums[fastIndex] != nums[slowIndex]){
nums[++slowIndex] = nums[fastIndex];
}
}
return slowIndex+1;//注意是+1 而不能是slowIndex++;//也可以是++slowIndex
}
}
283 移动零
【题目】
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
【示例】
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
【解答】
class Solution {
public void moveZeroes(int[] nums) {
int len = nums.length;
if(len ==0 || len == 1){
return ;
}
int slowIndex = 0;
for(int fastIndex = 0;fastIndex<len;fastIndex++){
if(nums[fastIndex] != 0){
nums[slowIndex++] = nums[fastIndex];
}
}//[0...slowIndex]保存非零数组
for(int i = slowIndex;i<len;i++){
nums[i] = 0;
}
}
}
844 比较含退格的字符串
【题目】
给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,请你判断二者是否相等。# 代表退格字符。
如果相等,返回 true ;否则,返回 false 。
【示例】
输入:s = "ab#c", t = "ad#c"
输出:true
解释:S 和 T 都会变成 “ac”。
输入:s = "ab##", t = "c#d#"
输出:true
解释:s 和 t 都会变成 “”。
输入:s = "a##c", t = "#a#c"
输出:true
解释:s 和 t 都会变成 “c”。
输入:s = "a#c", t = "b"
输出:false
解释:s 会变成 “c”,但 t 仍然是 “b”。
【解答】
——双指针法(两个指针分别指向两个字符串)
class Solution {
public boolean backspaceCompare(String s, String t) {
/**
每个字符需不需要被删除 取决于后面的退格符# 与前面的退格符无关
可以逆序遍历字符串,两者每找到一个有效字符时进行check(==true !=false)
怎么找有效字符
设置变量skip = 0;表示需要被删除的字符数(或者说不需要被用来比较的字符数)
用两个指针分别指向两个字符串
若s.charAt(i) =='#' 则skip++;i--;继续找有效字符
else 若 !='#'时 继续判断 若skip == 0,找到有效字符
若skip > 0,该字符被删除,用掉一个skip 同时i--
*/
int skip1 = 0,skip2 = 0;
int sIndex = s.length()-1,tIndex = t.length()-1;
while(sIndex >= 0 || tIndex >=0){//不能是 && 可能导致有一个字符串没有遍历完
//找s的下一有效字符
while(sIndex >= 0){
if(s.charAt(sIndex) == '#'){
skip1++;sIndex--;
}else if(skip1 > 0){//是字符 但是被删除
skip1--;sIndex--;
}else{
break;
}
}//跳出循环时 sIndex == -1 或找到有效字符
//找t的下一有效字符
while(tIndex >= 0){
if(t.charAt(tIndex) == '#'){
skip2++;tIndex--;
}else if(skip2 > 0){
skip2--;tIndex--;
}else{
break;
}
}
//开始比较
if(sIndex >= 0 && tIndex >= 0){
if(s.charAt(sIndex) != t.charAt(tIndex)){
return false;
}
}else{//有一个是 -1了
if(sIndex >= 0 || tIndex >= 0){
return false;//如有一个遍历结束 另一个还没遍历结束 则false
}
}
//不属于上述情况时
sIndex--;tIndex--;
}
return true;
}
}
——重构字符串
class Solution {
public boolean backspaceCompare(String s, String t) {
//StringBuilder builder = new StringBuilder(); 不建议 空间复杂度不是O(1)
String sreal = getRealStr(s);
String treal = getRealStr(t);
if(sreal.equals(treal)){
return true;
}else{
return false;
}
}
public String getRealStr(String str){
StringBuilder builder = new StringBuilder();
for(int i = 0;i<str.length();i++){
if(str.charAt(i) != '#'){
builder.append(str.charAt(i));
}else{
if(builder.length() != 0){
builder.deleteCharAt(builder.length()-1);
}
}
}
return builder.toString();
}
}
11 盛最多水的容器
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gHAsgHGN-1649300405775)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220220120237644.png)]
【示例】

输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
【解答】
class Solution {
public int maxArea(int[] height) {
int leftIndex = 0,rightIndex = height.length - 1;
int maxWater = 0;
while(leftIndex != rightIndex){
int water = (rightIndex - leftIndex)*Math.min(height[leftIndex],height[rightIndex]);
maxWater = water > maxWater?water:maxWater;
if(height[leftIndex] < height[rightIndex]){
leftIndex++;
}else{
rightIndex--;
}
}
return maxWater;
}
}
最长无重复子数组
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZmN68qV4-1649300405776)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220404164258967.png)]
import java.util.*;
public class Solution {
/**
*
* @param arr int整型一维数组 the array
* @return int整型
*/
public int maxLength (int[] arr) {
// write code here
if (arr.length < 2){
return arr.length;
}
Map<Integer,Integer> map = new HashMap<>();
map.put(arr[0],0);
int left = 0,right = left+1;
int max = 1;
while (left<right&&right<arr.length){
if (map.containsKey(arr[right])){
int index = map.get(arr[right]);
if (index<left){
map.put(arr[right],right);//更新下标
}else {
max = Math.max(max,right-left);
left = index+1;
map.put(arr[right],right);
}
}else {
map.put(arr[right],right);
}
right++;
}
return Math.max(max,right-left);
}
}
4、有序数组的平方
977 有序数组的平方
【题目】
给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
【示例】
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]
【解答】
——双指针 逆序保存结果
class Solution {
public int[] sortedSquares(int[] nums) {
int len = nums.length;
int res[] = new int[len];
int resIndex = len -1;
int left = 0,right = len - 1;
while(resIndex >= 0){
if(nums[right]*nums[right] >= nums[left]*nums[left]){
res[resIndex] = nums[right]*nums[right];
right--;
}else{
res[resIndex] = nums[left]*nums[left];
left++;
}
resIndex--;
}
return res;
}
}
——双指针 找到0为分界线的两个子数组 ← →保存结果
class Solution {
public int[] sortedSquares(int[] nums) {
int[] renums = new int[nums.length];
int neg = 0;
for(int i = 0;i<nums.length;i++){
if(nums[i]<=0){
neg = i;
}//找到非负整数的分界点 注意这里是<=0 若是>=0 后面的正数的索引会更新neg的值
nums[i] = nums[i] * nums[i];//对数组的每个值求平方
}
int left = neg,right = neg + 1;//非递增 [0...left] 非递减[right...n-1]
int already = 0;//已经插入到renums中的数组元素总数
while(left>=0 && right < nums.length){
if(nums[left]>nums[right]){
renums[already] = nums[right];
right++;
}else{
renums[already] = nums[left];
left--;
}
already++;
}
while(left>=0){
renums[already] = nums[left];
already++;
left--;
}
while(right<nums.length){
renums[already] = nums[right];
already++;
right++;
}
return renums;
}
}
5、长度最小的子数组–滑动窗口
**注:**理解滑动窗口如何移动 窗口起始位置,达到动态更新窗口大小的,从而得出长度最小的符合条件的长度。
滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)。
209 长度最小的子数组
【题目】
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存 在符合条件的子数组,返回 0 。
【示例】
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
输入:target = 4, nums = [1,4,4]
输出:1
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
【解答】
滑动窗口
class Solution {
public int minSubArrayLen(int target, int[] nums) {
//滑动窗口 [i,i+1,...i-1,j] 若窗口中sum<target 则j++;
//否则记录此时数组长度,且i++,找下一个满足条件的窗口
int result = Integer.MAX_VALUE;//结果
int i = 0;//窗口左边
int sum = 0;//记录窗口的总和
for(int j = 0;j<nums.length;j++){//右边窗口遍历整个数组
sum = sum + nums[j];
while(sum >= target){//当满足条件时,做相应修改
result = Math.min(result,j-i+1);//result 保留最小值
sum = sum - nums[i];
i++;//左边窗口右移
}
}
return result==Integer.MAX_VALUE?0:result;
}
}
904 水果成篮
【题目】
在一排树中,第 i 棵树产生 tree[i] 型的水果。
你可以从你选择的任何树开始,然后重复执行以下步骤:
1、把这棵树上的水果放进你的篮子里。如果你做不到,就停下来。
2、移动到当前树右侧的下一棵树。如果右边没有树,就停下来。
请注意,在选择一颗树后,你没有任何选择:你必须执行步骤 1,然后执行步骤 2,然后返回步骤 1,然后执行步骤 2,依此类 推,直至停止。【连续性:体现滑动窗口】
你有两个篮子**【体现窗口的限制】**,每个篮子可以携带任何数量的水果,但你希望每个篮子只携带一种类型的水果。
用这个程序你能收集的水果树的最大总量是多少?【窗口长度】
【示例】
输入:[1,2,1]
输出:3
解释:我们可以收集 [1,2,1]。
输入:[0,1,2,2]
输出:3
解释:我们可以收集 [1,2,2]
如果我们从第一棵树开始,我们将只能收集到 [0, 1]。
输入:[1,2,3,2,2]
输出:4
解释:我们可以收集 [2,3,2,2]
如果我们从第一棵树开始,我们将只能收集到 [1, 2]。
输入:[3,3,3,1,2,1,1,2,3,3,4]
输出:5
解释:我们可以收集 [1,2,1,1,2]
如果我们从第一棵树或第八棵树开始,我们将只能收集到 4 棵水果树。
【解答】
,class Solution {
public int totalFruit(int[] fruits) {
Map<Integer,Integer> map = new HashMap<>();// 类型:棵树
int i = 0;
int result = Integer.MIN_VALUE;
for(int j = 0;j<fruits.length;j++){
map.merge(fruits[j],1,Integer::sum);
if(map.keySet().size() <= 2){
result = Math.max(result,j-i+1);
System.out.println("len"+(j-i+1));
//将fruits[j]添加进去,若未曾出现则默认值为1,若出现过 则将当前值加1?
}else{//超过两种树 有三种树 滑动窗口右移 除去一种树
while(map.keySet().size() > 2){//等于两种树
map.put(fruits[i],map.get(fruits[i])-1);
if(map.get(fruits[i]) == 0){
map.remove(fruits[i]);
}
i++;
}//出循环时只有2种树 此时不用考虑更新result 因为此时的result一定比if中的短 或相等
}
}
return result == Integer.MIN_VALUE?0:result;
}
}
76 最小覆盖子串(未解答)
6、螺旋矩阵–模拟行为
【注】:每层循环时要保证要么都是左闭右闭,要么都是左闭右开,要保证一致
循环不变量原则
59 螺旋矩阵II
【题目】
给你一个正整数 n
,生成一个包含 1
到 n2
所有元素,且元素按顺时针顺序螺旋排列的 n x n
正方形矩阵 matrix
。
【示例】
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]
输入:n = 1
输出:[[1]]
【解答】
class Solution {
public int[][] generateMatrix(int n) {
int[][] matrix = new int[n][n];
int startX = 0,startY = 0;//[0,0] [1,1]... 每一层的开始坐标
int loop = n/2;//一共要进行多少层----注意向下取整 需要单独考虑最中间(奇数时)
int mid = n/2;
int offset = 1;//偏移量n - offset 为每一层的数字总数
int num = 1;//记录矩阵被赋值的数
while(loop>0){
int i = startX,j = startY;
//→ [)
for(;j<n-offset+startY;j++){
matrix[i][j] = num;
num++;//备用
}
//↓ [)
for(;i<n-offset+startX;i++){
matrix[i][j] = num;
num++;
}
//← [)
for(;j>startY;j--){
matrix[i][j] = num;
num++;
}
//↑ [)
for(;i>startX;i--){
matrix[i][j] = num;
num++;
}
loop--;
startX++;
startY++;
offset = offset + 2;
}
if(n%2 != 0){//奇数
matrix[mid][mid] = num;
}
return matrix;
}
}
54 螺旋矩阵 同剑指offer29
【注】此时用左闭右闭会少元素
【题目】
给你一个 m
行 n
列的矩阵 matrix
,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
【示例】
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
【解答–不同方向–visited数组记录】
class Solution {
public int[] spiralOrder(int[][] matrix) {
if(matrix.length == 0){
return new int[0];
}
int size = matrix.length*matrix[0].length;
boolean[][] visited = new boolean[matrix.length][matrix[0].length];//标记打印的数组
int[] res = new int[size];//返回数组
int i = 0,j = 0;
int[][] directions = {{0,1},{1,00},{0,-1},{-1,0}};
int dirIndex = 0;//记录方向的索引 在转换方向时index++ %4保证在这个方向序列中循环
for(int k = 0;k<size;k++){//每次循环的时候打印一个元素
res[k] = matrix[i][j];//记录打印元素
visited[i][j] = true;//标记已打印
int newi = i+directions[dirIndex][0];
int newj = j+directions[dirIndex][1];
if(newi<0||newi>=matrix.length||newj<0||newj>=matrix[0].length||visited[newi][newj] == true){
//不合适的索引时,改变索引 且更新新的newi newj
dirIndex = (dirIndex+1)%4;
newi = i+directions[dirIndex][0];
newj = j+directions[dirIndex][1];
}
i = newi;
j = newj;
}
return res;
}
}
【解答–螺旋矩阵–按层来】
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> list = new ArrayList<>();
int startX = 0,startY = 0;
int m = matrix.length,n = matrix[0].length;
int offset = 1;
while(list.size() < m*n){
int i = startX,j = startY;
//右[]
for(;j <= n-offset+startY&&list.size() < m*n;j++){//3-1+0=2
list.add(matrix[i][j]);
}
j--;//j指向最右边
//下()
for(i = startX+1;i <= m-offset+startX-1&&list.size() < m*n;i++){//3-1+0-1=1
list.add(matrix[i][j]);
}//出循环时 i = m-offset+startX
//左[]
for(;j >= startY&&list.size() < m*n;j--){
list.add(matrix[i][j]);
}
//上()
j++;
for(i = i-1;i > startX&&list.size() < m*n;i--){
list.add(matrix[i][j]);
}
startX++;
startY++;
offset = offset + 2;
}
return list;
}
}
·链表
1、理论知识
2、移除链表元素
注:有无虚拟头结点很重要
203 移除链表元素–leetcode
【题目】
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
【示例】
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
输入:head = [], val = 1
输出:[]
输入:head = [7,7,7,7], val = 7
输出:[]
【解答】
/**
* 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 removeElements(ListNode head, int val) {
ListNode dummy = new ListNode(-1,head);//设置虚拟头结点
ListNode pre = dummy;
ListNode cur = head;
while(cur != null){
if(cur.val == val){
pre.next = cur.next;//删除当前结点
}else{
pre = cur;//pre后移
}
cur = cur.next;
}
return dummy.next;
}
}
3、设计链表
注:灵活使用虚拟结点
【题目】
设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是 指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都 是 0-index 的。
在链表类中实现这些功能:
get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链 表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
【示例】
MyLinkedList linkedList = new MyLinkedList();
linkedList.addAtHead(1);
linkedList.addAtTail(3);
linkedList.addAtIndex(1,2); //链表变为1-> 2-> 3
linkedList.get(1); //返回2
linkedList.deleteAtIndex(1); //现在链表是1-> 3
linkedList.get(1); //返回3
【解答——双链表】
class DoubleLinkedNode{
int val = 0;
DoubleLinkedNode prev,next;
DoubleLinkedNode(){}
DoubleLinkedNode(int val){
this.val = val;
}
}
class MyLinkedList {
int size;
DoubleLinkedNode head;
DoubleLinkedNode tail;
//初始化
public MyLinkedList() {
size = 0;
head = new DoubleLinkedNode(0);
tail = new DoubleLinkedNode(0);
head.next = tail;
tail.prev = head;//其实是一个的头结点
}
//获取index位置的结点值:注意有虚拟头结点
public int get(int index) {
if(index < 0 || index >= size){return -1;}
//判断一下从头向后遍历 还是从末尾向前遍历
if(index < size/2){
DoubleLinkedNode cur = head;
for(int i = 0;i <= index;i++){
cur = cur.next;
}
return cur.val;
}else{
DoubleLinkedNode cur = tail;
for(int i = 0;i <= size-1-index;i++){
cur = cur.prev;
}
return cur.val;
}
}
public void addAtHead(int val) {
DoubleLinkedNode addNode = new DoubleLinkedNode(val);
addNode.next = head.next;
head.next.prev = addNode;
addNode.prev = head;
head.next = addNode;
size++;
}
//尾结点也是一个空的结点
public void addAtTail(int val) {
DoubleLinkedNode addNode = new DoubleLinkedNode(val);
DoubleLinkedNode cur = tail;
addNode.next = cur;
addNode.prev = cur.prev;
cur.prev.next = addNode;
cur.prev = addNode;
size++;
}
//在index位置添加一个结点
//由示例得index从0开始
public void addAtIndex(int index, int val) {
if(index > size){return;}
if(index < 0){index = 0;}
DoubleLinkedNode pre = head;
for(int i = 0;i<index;i++){
pre = pre.next;
}
DoubleLinkedNode addNode = new DoubleLinkedNode(val);
addNode.next = pre.next;
pre.next.prev = addNode;
pre.next = addNode;
addNode.prev = pre;
size++;
}
public void deleteAtIndex(int index) {
if(index < 0 || index >= size){
return;
}
DoubleLinkedNode pre = head;
for(int i = 0;i<index;i++){
pre = pre.next;
}
pre.next = pre.next.next;
pre.next.prev = pre;
size--;
}
}
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList obj = new MyLinkedList();
* int param_1 = obj.get(index);
* obj.addAtHead(val);
* obj.addAtTail(val);
* obj.addAtIndex(index,val);
* obj.deleteAtIndex(index);
*/
【解答——单链表】
//单链表
class LinkedNode{
int val;
LinkedNode next;
LinkedNode(){};
LinkedNode(int val){
this.val = val;
}
}
class MyLinkedList {
int size = 0;
LinkedNode head;//虚拟头结点
//初始化
public MyLinkedList() {
size = 0;
head = new LinkedNode(0);
}
//获取index位置的结点值:注意有虚拟头结点
public int get(int index) {
if(index<0 || index>=size){//索引无效
return -1;
}
LinkedNode cur = head;//当前结点指向虚拟头结点
for(int i = 0;i <= index;i++){
cur = cur.next;
}
return cur.val;
}
public void addAtHead(int val) {
addAtIndex(0,val);
}
public void addAtTail(int val) {
addAtIndex(size,val);
}
//在index位置添加一个结点
//由示例得index从0开始
public void addAtIndex(int index, int val) {
if(index > size){return ;}
//可以正常插入元素,注意size要更新
if(index < 0){index = 0;}
LinkedNode pre = head;
for(int i = 0;i<index;i++){
pre = pre.next;
}
LinkedNode addNode = new LinkedNode(val);
addNode.next = pre.next;
pre.next = addNode;
size++;
}
public void deleteAtIndex(int index) {
if(index >= size || index < 0){return;}//索引无效
//可以正常删除 注意size--
LinkedNode pre = head;
for(int i = 0;i<index;i++){
pre = pre.next;
}
pre.next = pre.next.next;
size--;
}
}
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList obj = new MyLinkedList();
* int param_1 = obj.get(index);
* obj.addAtHead(val);
* obj.addAtTail(val);
* obj.addAtIndex(index,val);
* obj.deleteAtIndex(index);
*/
4、翻转链表
双指针–递归
【题目】
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表
【示例】
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
输入:head = [1,2]
输出:[2,1]
输入:head = []
输出:[]
【解答-双指针】
/**
* 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) {
ListNode cur = head;
ListNode pre = null;
while(cur != null){
ListNode temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
}
}
【解答–递归】
/**
* 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) {
return reverse(null,head);
}
public ListNode reverse(ListNode pre,ListNode cur){
if(cur == null) return pre;
ListNode temp = cur.next;
cur.next = pre;
return reverse(cur,temp);
}
}
链表内指定区间反转
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类
* @param m int整型
* @param n int整型
* @return ListNode类
*/
public ListNode reverseBetween (ListNode head, int m, int n) {
// write code here
if(m == n) return head;
//找m位置mNode和前一位置mfront(有可能为空)
ListNode mNode,mFront,nNode,nNext;
int index = 0;
if(m == 1){
mNode = head;//mfront为空
mFront = null;
}else{
mFront = head;
index = 1;
while(index < m -1){
mFront = mFront.next;
index++;
}
mNode = mFront.next;
}
index++;
//找n位置和下一位置nNext
nNode = mNode;
while(index < n){
nNode = nNode.next;
index++;
}
nNext = nNode.next;
ListNode cur = mNode,pre = nNext;
while(cur != nNext){
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
if(m == 1){
return pre;
}else{
mFront.next = pre;
return head;
}
}
}
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类
* @param m int整型
* @param n int整型
* @return ListNode类
*/
public ListNode reverseBetween (ListNode head, int m, int n) {
//设置虚拟头节点
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode mNode,mFront,nNode,nNext;
mFront = dummy;mNode = dummy.next;
int index = 1;
//找m位置mNode和前一位置mfront
while(index < m){
mFront = mFront.next;
mNode = mNode.next;
index++;
}
nNode = mNode;
//找n位置和下一位置nNext
while(index < n){
nNode = nNode.next;
index++;
}
nNext = nNode.next;
ListNode cur = mNode,pre = nNext;
while(cur != nNext){
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
mFront.next = pre;
return dummy.next;
}
}
5、24两两交换链表中的结点
【注】:画示意图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2c950J9x-1649300405780)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20211110113516720.png)]
【题目】
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
【示例】
输入:head = [1,2,3,4]
输出:[2,1,4,3]
输入:head = []
输出:[]
【解答】
/**
* 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 swapPairs(ListNode head) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode cur = dummy;
while(cur.next != null && cur.next.next != null){//当前结点后面还有两个结点,还需要进行交换
ListNode temp1 = cur.next;//下一个cur
ListNode temp2 = cur.next.next.next;
cur.next = cur.next.next;//步骤1
cur.next.next = temp1;//步骤2
cur.next.next.next = temp2;
cur = temp1;
}
return dummy.next;
}
}
BM3 链表中的节点每k个一组翻转
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z1UdLNLA-1649300405781)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220323215319558.png)]
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类
* @param k int整型
* @return ListNode类
*/
public ListNode reverseKGroup (ListNode head, int k) {
// write code here
int nodeNum = 0;
ListNode p = head;
ListNode dummy = new ListNode(-1);
dummy.next = head;
while(p != null){
p = p.next;
nodeNum++;
}
int reverseNum = nodeNum/k;//2
int index = 0;
ListNode front = dummy,left = head,right = head,tail = front;
for(int i = 0;i<reverseNum;i++){
//每次旋转找到front tail
index = 0;
if(i != 0){
while(index < k){front = front.next;index++;}
}
left = front.next;
index = 0;right = front;
while(index < k){right = right.next;index++;}
tail = right.next;
reverse(front,left,tail);
}
return dummy.next;
}
public void reverse(ListNode front,ListNode cur,ListNode tail){
ListNode pre = tail;
while(cur != tail){
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
front.next = pre;
}
}
6、删除链表的倒数第N个结点
【注】:添加一个虚拟节点会比较好,注意手动模拟一下需要移动的位置
【题目】
给定一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
【示例】
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
输入:head = [1], n = 1
输出:[]
输入:head = [1,2], n = 1
输出:[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 removeNthFromEnd(ListNode head, int n) {
ListNode slow,fast;
ListNode dummy = new ListNode(0);
dummy.next = head;
slow = dummy;
fast = dummy;
int i = 0;
while(i<=n){
fast = fast.next;
i++;
}
while(fast != null){
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummy.next;
}
}
删除有序链表中重复的元素-II
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类
* @return ListNode类
*/
public ListNode deleteDuplicates (ListNode head) {
// write code here
if(head == null || head.next == null) return head;
//至少两个结点
ListNode dummy = new ListNode(1001);
dummy.next = head;
ListNode pre = dummy,cur = head;
while(cur != null){
ListNode next = cur.next;//肯定 存在的 但是可能为null
if(next != null && cur.val == next.val){//有相同的 注意判断next != null
while(cur.next != null && cur.val == cur.next.val){
cur.next = cur.next.next;
}
pre.next = cur.next;
}else{
pre = pre.next;
}
cur = cur.next;
}
return dummy.next;
}
}
7、链表相交
【题目】
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
图示两个链表在节点 c1 开始相交
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
【示例】
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode nodeA = headA;
ListNode nodeB = headB;
int lenA = 0,lenB = 0;//分别记录A B链表的长度
while(nodeA != null){//A的长度
lenA++;
nodeA = nodeA.next;
}
nodeA = headA;
while(nodeB != null){//B的长度
lenB++;
nodeB = nodeB.next;
}
nodeB = headB;
if(lenA > lenB){//移动到距离“交点”相同距离的位置
int skip = lenA - lenB;
while(skip>0){nodeA = nodeA.next;skip--;}
}else{
int skip = lenB - lenA;
while(skip>0){nodeB = nodeB.next;skip--;}
}
while(nodeA != nodeB){//找交点
nodeA = nodeA.next;
nodeB = nodeB.next;
}
return nodeB;
}
}
8、环形链表II
【注】:模拟,手动模拟,推理数学关系
【题目】
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表 中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。
说明:不允许修改给定的链表。
进阶:
你是否可以使用 O(1) 空间解决此题?
【解释】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i3BMhzuE-1649300405784)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20211110164854457.png)]
【示例】
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。
【解答】
/**
* 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 slow = head;//一次走一个结点
ListNode fast = head;//一次走两个结点
while(fast != null && fast.next != null){//注意while的判断条件
fast = fast.next.next;
slow = slow.next;
//找入口
if(fast == slow){//两者相遇,有环 开始找环的入口
ListNode index1 = head;
ListNode index2 = fast;
while(index1 != index2){
index1 = index1.next;
index2 = index2.next;
}
return index1;
}
}
return null;
}
}
链表相加
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head1 ListNode类
* @param head2 ListNode类
* @return ListNode类
*/
public ListNode addInList (ListNode head1, ListNode head2) {
// write code here
ListNode l1 = reverse(head1);
ListNode l2 = reverse(head2);
int curor = 0;
ListNode dummy = new ListNode(-1);
ListNode tail = dummy;
while(l1 != null && l2 != null){
int sum = l1.val + l2.val + curor;
l1.val = sum % 10;
curor = sum / 10;
tail.next = l1;
l1 = l1.next;
l2 = l2.next;
tail = tail.next;
}// l1 l2 至少一个为空
if(l2 != null){
l1 = l2;
}
while(l1 != null){
int sum = l1.val + curor;
l1.val = sum % 10;
curor = sum / 10;
tail.next = l1;
tail = tail.next;
l1 = l1.next;
}
//单独判断 进位
if(curor != 0){
ListNode node = new ListNode(curor);
node.next = null;
tail.next = node;
}
return reverse(dummy.next);
}
//链表翻转
public ListNode reverse(ListNode head){
ListNode dummy = new ListNode(-1);
dummy.next = null;
while(head != null){
ListNode next = head.next;
head.next = dummy.next;
dummy.next = head;
head = next;
}
return dummy.next;
}
}
单链表的排序
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类 the head node
* @return ListNode类
*/
public ListNode sortInList (ListNode head) {
// write code here
List<Integer> list = new ArrayList<>();
ListNode p = head;
while(p != null){
list.add(p.val);
p = p.next;
}
Integer[] a = list.toArray(new Integer[list.size()]);
Arrays.sort(a);
p = head;
int i = 0;
while(p != null){
p.val = a[i];
p = p.next;
i++;
}
return head;
}
}
判断一个链表是否为回文结构
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类 the head
* @return bool布尔型
*/
//将后半部分翻转
public boolean isPail (ListNode head) {
if(head == null && head.next == null) return true;
// write code here
ListNode fast = head,slow = head;
while(fast.next != null && fast.next.next != null){
fast = fast.next.next;
slow = slow.next;
}
ListNode tailHead;
if(fast.next == null){// [head,slow] = n [slow+1,tail] = n
tailHead = reverse(slow.next);
}else{//[head,slow] = n [slow+1,tail] = n+1
tailHead = reverse(slow.next.next);
}
slow.next = null;
slow = head;
while(tailHead != null && slow != null){
if(tailHead.val != slow .val) return false;
tailHead = tailHead.next;
slow = slow.next;
}
return true;
}
public ListNode reverse(ListNode head){
ListNode dummy = new ListNode(-1);
dummy.next = null;
ListNode p = head;
while(p != null){
ListNode next = p.next;
p.next = dummy.next;
dummy.next = p;
p = next;
}
return dummy.next;
}
}
链表的奇偶重排
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CHZUS2v5-1649300405787)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220324224558568.png)]
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* public ListNode(int val) {
* this.val = val;
* }
* }
*/
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* @param head ListNode类
* @return ListNode类
*/
public ListNode oddEvenList (ListNode head) {
if(head == null) return null;
// write code here
ListNode l0 = new ListNode(-1);//偶数位
l0.next = null;
ListNode l1 = new ListNode(-1);//奇数位
l1.next = null;
int index = 1;
ListNode p0 = l0,p1 = l1;
while(head != null){
ListNode next = head.next;
if(index%2 == 0){//偶数
p0.next = head;
p0 = p0.next;
p0.next = null;//注意这里要断开
}else{
p1.next = head;
p1 = p1.next;
p1.next = null;
}
head = next;
index++;
}
p1.next = l0.next;//这里是关键
return l1.next;
}
}
·哈希表
1、理论部分
当需要用到快速查询某个元素时,优先考虑哈希表
2、有效的字母异同词
242 有效的字母异同词
【题目】
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。
【示例】
输入: s = "anagram", t = "nagaram"
输出: true
输入: s = "rat", t = "car"
输出: false
【解答】
class Solution {
public boolean isAnagram(String s, String t) {
if(s.length() != t.length()){
return false;
}
int[] count = new int[26];
for(int i = 0;i<s.length();i++){
count[s.charAt(i)-'a']++;
}
for(int i = 0;i<t.length();i++){
count[t.charAt(i)-'a']--;
}
for(int i = 0;i<26;i++){
if(count[i] != 0){
return false;
}
}
return true;
}
}
383 赎金信
【题目】
给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串 ransom 能不能由第二个字符串 magazines 里面的字符构成。如果可以构成,返回 true ;否则返回 false。
(题目说明:为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思。杂志字符串中的每个字符只能在赎 金信字符串中使用一次。)
- 你可以假设两个字符串均只含有小写字母。
【示例】
输入:ransomNote = "a", magazine = "b"
输出:false
输入:ransomNote = "aa", magazine = "ab"
输出:false
输入:ransomNote = "aa", magazine = "aab"
输出:true
【解答】
class Solution {
public boolean canConstruct(String ransomNote, String magazine) {
if(magazine.length() < ransomNote.length()){//做一下长度判断 可提前返回
return false;
}
int[] counts = new int[26];
for(int i = 0;i<magazine.length();i++){
counts[magazine.charAt(i)-'a']++;
}
for(int i = 0;i<ransomNote.length();i++){
counts[ransomNote.charAt(i)-'a']--;
}
for(int i = 0;i<26;i++){
if(counts[i] < 0){
return false;
}
}
return true;
}
}
49 字母异位词分组(可继续做)
【题目】
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的字母得到的一个新单词,所有源单词中的字母都恰好只用一次。
【示例】
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
输入: strs = [""]
输出: [[""]]
输入: strs = ["a"]
输出: [["a"]]
【解答】
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
List<List<String>> res = new ArrayList<>();
boolean[] visted = new boolean[strs.length];
int i = 0;
while(i<strs.length){
if(!visted[i]){
List<String> list = new ArrayList<>();
list.add(strs[i]);
visted[i] = true;
int j = i + 1;
while(j<strs.length){
if(!visted[j]){
if(isAnagram(strs[i],strs[j])){//strs[i] strs[j]是字母异同词
list.add(strs[j]);
visted[j] = true;
}
}
j++;
}
res.add(list);
}
i++;
}
return res;
}
public boolean isAnagram(String s, String t) {
if(s.length() != t.length()){
return false;
}
int[] count = new int[26];
for(int i = 0;i<s.length();i++){
count[s.charAt(i)-'a']++;
}
for(int i = 0;i<t.length();i++){
count[t.charAt(i)-'a']--;
}
for(int i = 0;i<26;i++){
if(count[i] != 0){
return false;
}
}
return true;
}
}
438 找到字符串中所有字母异位词(可继续做)
【题目】
给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
【示例】
输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。
输入: s = "abab", p = "ab"
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。
【解答】
class Solution {
public List<Integer> findAnagrams(String s, String p) {
char[] keychar = p.toCharArray();
Arrays.sort(keychar);
List<Integer> res = new ArrayList<>();
for(int i = 0;i <= s.length()-p.length();i++){
String str = s.substring(i,i+p.length());
char[] array = str.toCharArray();
Arrays.sort(array);
if(Arrays.equals(array,keychar)){
res.add(i);
}
}
return res;
}
}
3、两个数组的交集–[HashSet]
349 两个数组的交集
【注】:HashSet ArrayList不能直接转int[]
【题目】
给定两个数组,编写一个函数来计算它们的交集。
【示例】
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
【解答】
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
Set<Integer> refer = new HashSet<>();
for(int i = 0;i<nums1.length;i++){//记录num1中出现的数字的集合
refer.add(nums1[i]);
}
Set<Integer> res = new HashSet<>();
for(int i = 0;i<nums2.length;i++){//求交集
if(refer.contains(nums2[i])){
res.add(nums2[i]);
}
}
int[] result = new int[res.size()];//赋值 求结果集
int i = 0;
for(int num:res){//注意没有res.get()函数
result[i] = num;
i++;
}
return result;
}
}
350 两个数组的交集(可继续)
【题目】
给定两个数组,编写一个函数来计算它们的交集。
【示例】
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]
【解答】
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
Map<Integer,Integer> map1 = getCurTimes(nums1);
Map<Integer,Integer> map2 = getCurTimes(nums2);
Map<Integer,Integer> mapres = getUnion(map1,map2);
List<Integer> reslist = new ArrayList<>();
//构造结果集
for(int key:mapres.keySet()){
for(int i = 0;i<mapres.get(key);i++){
reslist.add(key);
}
}
int[] res = new int[reslist.size()];
//转成int[]
for(int i = 0;i<reslist.size();i++){
res[i] = reslist.get(i);
}
return res;
}
public Map<Integer,Integer> getCurTimes(int[] nums){//求数组中各数字出现的次数
Map<Integer,Integer> map = new HashMap<>();
for(int i = 0;i<nums.length;i++){
map.merge(nums[i],1,Integer::sum);
}
return map;
}
//求交集以及保存出现次数的较小值
public Map<Integer,Integer> getUnion(Map<Integer,Integer> map1,Map<Integer,Integer> map2){
Map<Integer,Integer> map = new HashMap<>();
for(int i:map1.keySet()){
if(map2.containsKey(i)){
int time1 = map1.get(i),time2 = map2.get(i);
int time = time1 < time2?time1:time2;
map.put(i,time);
}
}
return map;
}
}
【可继续】用双指针||||上述方法,还可以简化,只遍历一次得出结果集
4、202 快乐数
【题目】
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果 可以变为 1,那么这个数就是快乐数。
如果 n 是快乐数就返回 true ;不是,则返回 false 。
【示例】
输入:19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
输入:n = 2
输出:false
【解答】
class Solution {
public boolean isHappy(int n) {
Set<Integer> list = new HashSet<>();
int num = getNextnum(n);
while(n != 1 && !list.contains(num)){
list.add(num);
num = getNextnum(num);
}
return num==1;
}
public int getNextnum(int n){
int sum = 0;
while(n != 0){
int num = n%10;
sum = sum + num*num;
n = n/10;
}
return sum;
}
}
5、两数之和
1 两数之和–[HashMap]
【题目】
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组 下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案
【示例】
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
输入:nums = [3,2,4], target = 6
输出:[1,2]
输入:nums = [3,3], target = 6
输出:[0,1]
【解答】
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer,Integer> map = new HashMap<>();
for(int i = 0;i<nums.length;i++){
int addLeft = nums[i];
if(map.containsKey(target-addLeft)){
return new int[]{i,map.get(target-addLeft)};
}else{
map.put(addLeft,i);
}
}
return new int[2];
}
}
6、三数之和
15 三数之和
【题目】
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不 重复的三元组。
注意:答案中不可以包含重复的三元组。【去重:最好是求结果的时候就去重,减少比较次数】
【示例】
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
输入:nums = []
输出:[]
输入:nums = [0]
输出:[]
【解答】
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
//一个for循环 每个循环中有left right两个指针
List<List<Integer>> resList = new ArrayList<>();
//先将nums数组排序 递增序列 --->
Arrays.sort(nums);
for(int i = 0;i<nums.length;i++){
//遇到大于0的元素(则之后的元素都比0大了,就没有必要再查找了)
if(nums[i]>0){
return resList;
}
//去重:若当前元素与上一个一样,则表示已经查找过了
//注意是nums[i]与nums[i-1]比较,而不是nums[i+1]比较
if(i>0&&nums[i] == nums[i-1]){
continue;
}
int left = i+1,right = nums.length-1;//双指针
while(left<right){//注意循环退出条件,同一个元素不能被用两次
int sum = nums[i]+nums[left]+nums[right];
if(sum>0){
right--;
}else if(sum<0){
left++;
}else{//找到结果,存入元组,注意继续去重
List<Integer> insertList = new ArrayList<>();
insertList.add(nums[i]);
insertList.add(nums[left]);
insertList.add(nums[right]);
resList.add(insertList);
while(left<right && nums[right]==nums[right-1]){right--;}
//此时的nums[right]对应的数值
while(left<right && nums[left]==nums[left+1]){left++;}
left++;
right--;
}
}
}
return resList;
}
}
7、四数之和
18 四数之和
【题目】
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
【示例】
输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]
【解答】
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> res = new ArrayList<>();
int len = nums.length;
int left,right;
Arrays.sort(nums);//递增排序
for(int i = 0;i < len;i++){
if(i>0&&nums[i-1]==nums[i]){
continue;
}
for(int j = i+1;j<len;j++){
if(j>i+1&&nums[j-1]==nums[j]){//注意判断条件
continue;
}
left = j+1;right = len - 1;
while(left<right){
int sum = nums[i] + nums[j] + nums[left] + nums[right];
if(sum>target){
right--;
}else if(sum<target){
left++;
}else{
res.add(Arrays.asList(nums[i],nums[j],nums[left],nums[right]));
while(right>left&&nums[left]==nums[left+1]){left++;}//剪枝
while(right>left&&nums[right]==nums[right-1]){right--;}
left++;right--;
}
}
}
}
return res;
}
}
454 四数相加II
【题目】
给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:
0 <= i, j, k, l < n nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
【示例】
输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出:2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0
输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]
输出:1
【解答】
class Solution {
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
Map<Integer,Integer> mapAB = new HashMap<>();
//统计a+b出现的次数
for(int a:nums1){
for(int b:nums2){
mapAB.merge(a+b,1,Integer::sum);
}
}
int count = 0;//统计相加和为0的次数
for(int c:nums3){
for(int d:nums4){
if(mapAB.containsKey(0-c-d)){
count = count + mapAB.get(0-c-d);
}
}
}
return count;
}
}
·字符串
1、翻转字符串
【题目】
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
【示例】
输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]
输入:s = ["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]
【解答】
class Solution {
public void reverseString(char[] s) {
char temp;
int left = 0,right = s.length -1;
while(left<=right){
temp = s[left];
s[left] = s[right];
s[right] = temp;
left++;
right--;
}
}
}
2、反转字符串II
【题目】
给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。
如果剩余字符少于 k 个,则将剩余字符全部反转。
如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。
【示例】
输入:s = "abcdefg", k = 2
输出:"bacdfeg"
输入:s = "abcd", k = 2
输出:"bacd"
【解答】
class Solution {
public String reverseStr(String s, int k) {
if(s.length()==1){
return s;
}
StringBuilder res = new StringBuilder();//也可以转化为char[] s.toCharArray()
res.append(s);
for(int i = 0;i<s.length();i = i+2*k){
int left = i;
int right;
if(i+k-1>s.length()-1){
right = s.length()-1;
}else{
right = i+k-1;
}
//交换字符
while(left<right){
res.setCharAt(left,s.charAt(right));
res.setCharAt(right,s.charAt(left));
left++;
right--;
}
}
return res.toString();
}
}
3、替换空格
【题目】
请实现一个函数,把字符串 s
中的每个空格替换成"%20"。
【示例】
输入:s = "We are happy."
输出:"We%20are%20happy."
【解答】
双指针
class Solution {
public String replaceSpace(String s) {
int spaceNum = 0;
for(int i = 0;i<s.length();i++){
if(s.charAt(i) == ' '){
spaceNum++;
}
}
if(spaceNum == 0){
return s;
}
char[] schar = new char[s.length()+2*spaceNum];
int left = s.length()-1;
int right = schar.length-1;
while(right >= 0){//right 可以为 0
char newchar = s.charAt(left);
if(newchar != ' '){
schar[right] = newchar;
}else{//是空格
schar[right] = '0';
schar[--right] = '2';
schar[--right] = '%';
}
left--;right--;
}
return new String(schar);//不能用schar.toString()
}
}
StringBuilder
class Solution {
public String replaceSpace(String s) {
if (s.length() == 0){//空字符串时直接返回
return s;
}
StringBuilder builder = new StringBuilder();
for (int i = 0;i<s.length();i++){
if (s.charAt(i) != ' '){
builder.append(s.charAt(i));
}else {
builder.append("%20");
}
}
return builder.toString();
}
}
4、反转字符串中的单词
【题目】
给你一个字符串 s ,逐个翻转字符串中的所有 单词 。
单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。
请你返回一个翻转 s 中单词顺序并用单个空格相连的字符串。
说明:
输入字符串 s 可以在前面、后面或者单词间包含多余的空格。
翻转后单词间应当仅用一个空格分隔。
翻转后的字符串中不应包含额外的空格。
【示例】
输入:s = "the sky is blue"
输出:"blue is sky the"
输入:s = " hello world "
输出:"world hello"
解释:输入字符串可以在前面或者后面包含多余的空格,但是翻转后的字符不能包括。
输入:s = "a good example"
输出:"example good a"
解释:如果两个单词间有多余的空格,将翻转后单词间的空格减少到只含一个。
输入:s = " Bob Loves Alice "
输出:"Alice Loves Bob"
输入:s = "Alice does not even like bob"
输出:"bob like even not does Alice"
【解答】
class Solution {
public String reverseWords(String s) {
StringBuilder sb = removeSpace(s);
reverseString(sb,0,sb.length()-1);
int start = 0,end = start+1;
while(end<sb.length()){
while(end<sb.length()&&sb.charAt(end) != ' '){end++;}//找到下一个空格,end是空格的索引
reverseString(sb,start,end-1);
start = end+1;
end = start+1;
}
return sb.toString();
}
public StringBuilder removeSpace(String s){
int end = s.length()-1;
while(s.charAt(end)==' '){end--;}//删除末尾空格
int start = 0;
while(s.charAt(start)==' '){start++;}//删除开头空格
StringBuilder sb = new StringBuilder();
for(int i = start;i<=end;i++){
if(s.charAt(i)!=' '||i>0&&s.charAt(i-1)!=' '){
sb.append(s.charAt(i));
}
}
return sb;
}
public void reverseString(StringBuilder sb,int start,int end){
while(start<end){
char temp = sb.charAt(start);
sb.setCharAt(start,sb.charAt(end));
sb.setCharAt(end,temp);
start++;
end--;
}
}
}
5、左旋转字符串
【题目】
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输 入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
【示例】
输入: s = "abcdefg", k = 2
输出: "cdefgab"
输入: s = "lrloseumgh", k = 6
输出: "umghlrlose"
【解答】
class Solution {
public String reverseLeftWords(String s, int n) {
StringBuilder sb = new StringBuilder();
sb.append(s);
reverseWord(sb,0,n-1);
reverseWord(sb,n,sb.length()-1);
reverseWord(sb,0,sb.length()-1);
return sb.toString();
}
public void reverseWord(StringBuilder sb,int start,int end){
while(start<end){
char temp = sb.charAt(start);
sb.setCharAt(start,sb.charAt(end));
sb.setCharAt(end,temp);
start++;
end--;
}
}
}
class Solution {
public String reverseLeftWords(String s, int n) {
return s.substring(n,s.length())+s.substring(0,n);
}
}
最长公共前缀
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rOkTxaWT-1649300405789)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220325160848157.png)]
import java.util.*;
public class Solution {
/**
*
* @param strs string字符串一维数组
* @return string字符串
*/
public String longestCommonPrefix (String[] strs) {
// write code here
if(strs.length == 0) return "";
char[] prefix = strs[0].toCharArray();
int resIndex = prefix.length;
for(int i = 0;i<resIndex;i++){//水平比较
for(int j = 1;j<strs.length;j++){
if(strs[j].length() == i||strs[j].charAt(i) != prefix[i]){//注意第一个判断条件
return new String(prefix,0,i);
}
}
}
return new String(prefix);
}
}
【题目】
【示例】
【解答】
·双指针法
【1】
数组:原地移除元素
【2】
字符串:填充类、删除冗余空格
【3】
链表:翻转、删除、找环
1、移除元素
27 移除元素
【题目】
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
【示例】
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。
【解答】
class Solution {
public int removeElement(int[] nums, int val) {
int slowIndex = 0;//记录不等于val的数组下标
for(int fastIndex = 0;fastIndex<nums.length;fastIndex++){
if(nums[fastIndex] != val){
nums[slowIndex] = nums[fastIndex];
slowIndex++;//下次使用
}
}
return slowIndex;
}
}
26 删除排序数组中的重复项
【题目】
给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
【示例】
输入:nums = [1,1,2]
输出:2, nums = [1,2]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。
输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。
【解答】
class Solution {
public int removeDuplicates(int[] nums) {
if(nums.length ==0 || nums.length == 1){
return nums.length;
}
int slowIndex = 0;
for(int fastIndex = 1;fastIndex<nums.length;fastIndex++){
if(nums[fastIndex] != nums[slowIndex]){
nums[++slowIndex] = nums[fastIndex];
}
}
return slowIndex+1;//注意是+1 而不能是slowIndex++;//也可以是++slowIndex
}
}
283 移动零
【题目】
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
【示例】
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
【解答】
双指针:左指针指向的都是非零数据,右指针指向待处理元素,使用元素覆盖,最后重置左指针后面的元素为0
class Solution {
public void moveZeroes(int[] nums) {
int len = nums.length;
if(len ==0 || len == 1){
return ;
}
int slowIndex = 0;
for(int fastIndex = 0;fastIndex<len;fastIndex++){
if(nums[fastIndex] != 0){
nums[slowIndex++] = nums[fastIndex];
}
}//[0...slowIndex]保存非零数组
for(int i = slowIndex;i<len;i++){
nums[i] = 0;
}
}
}
双指针:左指针左边都是非零数据,右指针右边都是待处理的元素,左右指针中间是0,每次遇到非零元素,则交换左右指针指向的元素值
class Solution {
public void moveZeroes(int[] nums) {
int left = 0,right = 0;
for(right = 0;right<nums.length;right++){
if(nums[right]!=0){//遇到非零数据
swap(nums,left,right);
left++;//准备保存下一非零数值
}
}
}
public void swap(int[] nums,int left,int right){
int temp = nums[right];
nums[right] = nums[left];
nums[left] = temp;
}
}
844 比较含退格的字符串
【题目】
给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,请你判断二者是否相等。# 代表退格字符。
如果相等,返回 true ;否则,返回 false 。
注意:如果对空文本输入退格字符,文本继续为空。
【示例】
输入:s = "ab#c", t = "ad#c"
输出:true
解释:S 和 T 都会变成 “ac”。
输入:s = "ab##", t = "c#d#"
输出:true
解释:s 和 t 都会变成 “”。
输入:s = "a##c", t = "#a#c"
输出:true
解释:s 和 t 都会变成 “c”。
输入:s = "a#c", t = "b"
输出:false
解释:s 会变成 “c”,但 t 仍然是 “b”。
【解答】
class Solution {
public boolean backspaceCompare(String s, String t) {
int skips = 0,skipt = 0;
int indexs = s.length()-1;
int indext = t.length()-1;
while(indexs>=0 || indext>=0){
while(indexs>=0){//找第一个有效字符
if(s.charAt(indexs) == '#'){
skips++;indexs--;
}else if(skips>0){//是字符,但是因为后续有skip,所以需要删除,继续寻找有效字符
skips--;//用掉一个#
indexs--;
}else{
break;//找到有效字符
}
}
while(indext>=0){//找第一个有效字符
if(t.charAt(indext) == '#'){
skipt++;indext--;
}else if(skipt>0){//是字符,但是因为后续有skip,所以需要删除,继续寻找有效字符
skipt--;//用掉一个#
indext--;
}else{
break;//找到有效字符
}
}
if(indexs>=0 && indext>=0){
if(s.charAt(indexs) != t.charAt(indext)){
return false;
}
}else if(indexs>=0 || indext>=0){
//有一个已经遍历结束,即有一个没有找到有效字符,而另一个找到了
return false;
}
indexs--;indext--;
}
return true;
}
}
977 有序数组的平方
【题目】
给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
【示例】
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]
提示:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums
已按 非递减顺序 排序
【解答】
class Solution {
public int[] sortedSquares(int[] nums) {
int len = nums.length;
int res[] = new int[len];
int resIndex = len -1;
int left = 0,right = len - 1;
while(resIndex >= 0){//逆序保存
if(nums[right]*nums[right] >= nums[left]*nums[left]){
res[resIndex] = nums[right]*nums[right];
right--;
}else{
res[resIndex] = nums[left]*nums[left];
left++;
}
resIndex--;
}
return res;
}
}
2、反转字符串
3、替换空格
4、翻转字符串里的单词
5、翻转链表
6、删除链表的倒数第N个节点
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类
* @param n int整型
* @return ListNode类
*/
public ListNode removeNthFromEnd (ListNode head, int n) {
// if(head == null) return null;
// write code here
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy,pnode = dummy;
int index = 0;
while(index <= n){
pnode = pnode.next;
index++;
}
while(pnode != null){
pnode = pnode.next;
pre = pre.next;
}
pre.next = pre.next.next;
return dummy.next;
}
}
7、链表相交
8、环形链表II
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qnXPxfRk-1649300405789)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220324151200019.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kWYWShhT-1649300405790)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220324151212682.png)]
/**
* 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) return false;
ListNode fast = head,slow = head;
while(fast.next != null && fast.next.next != null && slow.next != null){
fast = fast.next.next;
slow = slow.next;
if(slow == fast) return true;
}
return false;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OynwaRp3-1649300405790)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220324170308376.png)]
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead) {
ListNode fast = pHead,slow = pHead;
while(fast.next != null && fast.next.next != null && slow != null){
fast = fast.next.next;
slow = slow.next;
if(slow == fast){//有环
ListNode resNode = pHead;
while(resNode != slow){
resNode = resNode.next;
slow = slow.next;
}
return resNode;
}
}
return null;
}
}
合并K个已排序的链表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1GyPbaNR-1649300405790)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220324153023687.png)]
import java.util.*;
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode mergeKLists(ArrayList<ListNode> lists) {
return mergeList(lists,0,lists.size()-1);
}
public ListNode mergeList(ArrayList<ListNode> lists,int left,int right){
if(left == right) return lists.get(left);
if(left>right) return null;
int mid = ((right - left)>>1) + left;
return merge(mergeList(lists,left,mid),mergeList(lists,mid+1,right));
}
public ListNode merge(ListNode l1,ListNode l2){
if(l1 == null) return l2;
if(l2 == null) return l1;
ListNode dummy = new ListNode(-1);
ListNode temp = dummy;
while(l1 != null && l2 != null){
if(l1.val < l2.val){
temp.next = l1;
l1 = l1.next;
}else{
temp.next = l2;
l2 = l2.next;
}
temp = temp.next;
}
if(l1 != null) temp.next = l1;
if(l2 != null) temp.next = l2;
return dummy.next;
}
}
9、三数之和
10、四数之和
·栈与队列
1、用栈实现队列
【题目】
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):实现 MyQueue 类:
void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
你只能使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
【示例】
输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]
解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false
【解答】
class MyQueue {
private Stack<Integer> A;
private Stack<Integer> B;//辅助栈 --全局变量
public MyQueue() {
A = new Stack<>();
B = new Stack<>();
}
public void push(int x) {
A.push(x);
}
public int pop() {
int res = 0;//不初始化不能使用---但是在此OJ上不报错
if(B.isEmpty()){
while(!A.isEmpty()){
B.push(A.pop());
}
}
res = B.pop();
return res;
}
public int peek() {
int res = 0;//不初始化不能使用
if(B.isEmpty()){
while(!A.isEmpty()){
B.push(A.pop());
}
}
res = B.peek();
return res;
}
public boolean empty() {
if(A.isEmpty()&&B.isEmpty()){
return true;
}else{
return false;
}
}
}
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue obj = new MyQueue();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.peek();
* boolean param_4 = obj.empty();
*/
2、用队列实现栈
【题目】
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
注意:
你只能使用队列的基本操作 —— 也就是 push to back、peek/pop from front、size 和 is empty 这些操作。
你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
【示例】
输入:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]
解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False
【解答】
两种方式
class MyStack {
Queue<Integer> A;
//Queue<Integer> B;//也可以借助辅助栈 此时栈在出队时 保存队内元素
public MyStack() {
A = new LinkedList<>();
//B = new LinkedList<>();
}
public void push(int x) {
A.add(x);
}
public int pop() {
int times = A.size()-1; //从队头出 再插入队尾
while(times>0){
A.add(A.remove());
times--;
}
return A.remove();
}
public int top() {
int times = A.size()-1; //从队头出 再插入队尾
while(times>0){
A.add(A.remove());
times--;
}
int res = A.peek();
A.add(A.remove());
return res;
}
public boolean empty() {
return A.isEmpty();
}
}
/**
* 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();
*/
3、有效的括号
【题目】
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
【示例】
输入:s = "()"
输出:true
输入:s = "()[]{}"
输出:true
输入:s = "(]"
输出:false
输入:s = "([)]"
输出:false
输入:s = "{[]}"
输出:true
【解答】
注意:Deque stack = new LinkedList<>();可表示栈:使用:peek() push() pop()
class Solution {
public boolean isValid(String s) {
if(s.length()%2==1){//长度是奇数
return false;
}
Stack<Character> stack = new Stack<>();
for(int i = 0;i<s.length();i++){
char c = s.charAt(i);//取出符号
if(c == '{' || c == '[' || c == '('){
stack.push(c);
}else if(stack.isEmpty()){//是右括号 但是栈中没有左括号
return false;
}else{
char cp = stack.peek();//取出栈顶元素---左括号
if(c =='}' && cp=='{' || c ==']' && cp=='['||c ==')' && cp=='('){
stack.pop();//匹配成功,弹出栈顶元素
continue;
}else{
return false;//出现不匹配,直接返回
}
}
}
return stack.isEmpty();
}
}
4、删除字符串中的所有相邻重复项
【题目】
给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
【示例】
输入:"abbaca"
输出:"ca"
解释:
例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。
【解答】
使用栈和StringBuilder
class Solution {
public String removeDuplicates(String s) {
Stack<Character> stack = new Stack<>();
for(int i = 0;i<s.length();i++){
char c = s.charAt(i);
if(!stack.isEmpty() && c == stack.peek()){//出现相邻重复字符
stack.pop();
}else{
stack.push(c);
}
}
StringBuilder sb = new StringBuilder();
while(!stack.isEmpty()){//存入StringBuilder 注意这里不能使用setCharAt sb此时是空的
sb.append(stack.pop());
}
int left = 0,right = sb.length()-1;
while(left<right){//翻转
char temp = sb.charAt(right);
sb.setCharAt(right,sb.charAt(left));
sb.setCharAt(left,temp);
left++;right--;
}
return sb.toString();
}
}
双指针
class Solution {
public String removeDuplicates(String s) {
if(s.length() == 1){
return s;
}
char[] schar = s.toCharArray();
int left = 0,right = left+1;//left保存待返回数组的最右边的字符指针 right指针待处理字符
while(right<s.length()){
if(left<0||schar[right]!=schar[left]){//注意需要特殊判断left的值
schar[++left] = schar[right];
}else{
left--;
}
right++;
}
return new String(schar,0,left+1);
}
}
5、逆波兰表达式求值
【题目】
根据 逆波兰表示法,求表达式的值。
有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
说明:
整数除法只保留整数部分。
给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
【示例】
输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]
输出:22
解释:
该算式转化为常见的中缀算术表达式为:
((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22
【解答】
**注意:String 类型 转Integer:Integer.valueOf(s) Integer.parseInt(s) **
class Solution {
public int evalRPN(String[] tokens) {//注意是String数组
Stack<Integer> numStack = new Stack<>();
for(int i = 0;i<tokens.length;i++){
String token = tokens[i];
//注意字符串判断时 不能直接使用== 而应该使用equals()
if(token.equals("+")||token.equals("-")||token.equals("*")||token.equals("/")){
int rnum = numStack.pop();
int lnum = numStack.pop();
numStack.push(operation(lnum,rnum,token));
}else{
numStack.push(Integer.parseInt(token));//Integer.valueOf(s)
}
}
return numStack.pop();
}
public int operation(int lnum,int rnum,String oper){
if(oper.equals("+")){
return lnum + rnum;
}else if(oper.equals("-")){
return lnum - rnum;
}else if(oper.equals("*")){
return lnum * rnum;
}else{
return lnum / rnum;
}
}
}
6、滑动窗口最大值
【题目】
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数 字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
【示例】
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
输入:nums = [1], k = 1
输出:[1]
输入:nums = [1,-1], k = 1
输出:[1,-1]
输入:nums = [9,11], k = 2
输出:[11]
输入:nums = [4,-2], k = 2
输出:[4]
【解答】
class MyQueue{
Deque<Integer> deque = new LinkedList<>();
//弹出元素
public void poll(int val){
if(!deque.isEmpty() && val == deque.peek()){
deque.poll();
}
}
//添加元素--这里的getLast其实是队尾的元素--以此维持队列的单调性
public void add(int val){
while(!deque.isEmpty()&& val > deque.getLast()){
deque.removeLast();
}
deque.add(val);
}
//返回队头元素
public int front(){
return deque.peek();
}
}
class Solution {
//重新定义push pop函数
public int[] maxSlidingWindow(int[] nums, int k) {
if(k == 1){
return nums;
}
int[] res = new int[nums.length - k + 1];
int indexres = 0;
MyQueue myqueue = new MyQueue();
for(int i = 0;i<k;i++){
myqueue.add(nums[i]);
}
res[indexres++] = myqueue.front();
for(int i = k;i<nums.length;i++){
myqueue.poll(nums[i-k]);
myqueue.add(nums[i]);
res[indexres++] = myqueue.front();
}
return res;
}
}
7、出现频次最高的K个元素–小顶堆
【题目】
给你一个整数数组 nums
和一个整数 k
,请你返回其中出现频率前 k
高的元素。你可以按 任意顺序 返回答案。
【示例】
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
输入: nums = [1], k = 1
输出: [1]
【解答】
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer,Integer> map = new HashMap<>();
for(int i = 0;i<nums.length;i++){
map.merge(nums[i],1,Integer::sum);//统计出现的次数
}
//定义优先队列 重新定义排序方式--lambda表达式--正序排--小顶堆
PriorityQueue<Map.Entry<Integer,Integer>> queue = new PriorityQueue<>((e1,e2)->e1.getValue()-e2.getValue());
for(Map.Entry<Integer,Integer> entry:map.entrySet()){
queue.offer(entry);
if(queue.size() > k){
queue.poll();//出堆 出去的是当前最小的
}
}
int[] res = new int[k];
for(int i = k-1;i>=0;i--){
res[i] = queue.poll().getKey();//获得键值
}
return res;
}
}
·二叉树
1、二叉树的递归遍历
这里帮助大家确定下来递归算法的三个要素。每次写递归,都按照这三要素来写,可以保证大家写出正确的递归算法!
- 确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
- 确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
- 确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
2、二叉树的迭代遍历
注意访问顺序和迭代顺序是否一致
141 二叉树的前序遍历
【题目】
给你二叉树的根节点 root
,返回它节点值的 前序 遍历。
【示例】
输入:root = [1,null,2,3]
输出:[1,2,3]
输入:root = []
输出:[]
【解答】
递归
/**
* 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 {
List<Integer> res = new ArrayList<>();
public List<Integer> preorderTraversal(TreeNode root) {
traversal(root,res);
return res;
}
public void traversal(TreeNode cur,List<Integer> res){
if(cur == null){
return;
}
res.add(cur.val);
traversal(cur.left,res);
traversal(cur.right,res);
}
}
迭代
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
if(root == null){
return res;
}else{
stack.push(cur);
}
while(!stack.isEmpty()){//栈不为空
cur = stack.pop();
res.add(cur.val);
if(cur.right!=null){stack.push(cur.right);}
if(cur.left!=null){stack.push(cur.left);}
}
return res;
}
}
94 二叉树的中序遍历
【题目】
给定一个二叉树的根节点 root
,返回它的 中序 遍历。
【示例】
输入:root = [1,null,2,3]
输出:[1,3,2]
输入:root = []
输出:[]
【解答】
递归
/**
* 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 {
List<Integer> res = new ArrayList<>();
public List<Integer> inorderTraversal(TreeNode root) {
traversal(root,res);
return res;
}
public void traversal(TreeNode cur,List<Integer> res){
if(cur == null) return;
traversal(cur.left,res);
res.add(cur.val);
traversal(cur.right,res);
}
}
迭代
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while(cur != null || !stack.isEmpty()){
if(cur != null){
stack.push(cur);
cur = cur.left;
}else{
cur = stack.pop();
res.add(cur.val);
cur = cur.right;
}
}
return res;
}
}
145 二叉树的后序遍历
【题目】
给定一个二叉树,返回它的 后序 遍历。
【示例】
输入: [1,null,2,3]
1
\
2
/
3
输出: [3,2,1]
【解答】
递归
/**
* 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 {
List<Integer> res = new ArrayList<>();
public List<Integer> postorderTraversal(TreeNode root) {
traversal(root,res);
return res;
}
public void traversal(TreeNode cur,List<Integer> res){
if(cur == null) return;
traversal(cur.left,res);
traversal(cur.right,res);
res.add(cur.val);
}
}
迭代
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
if(cur == null){
return res;
}else{
stack.push(cur);
}
while(!stack.isEmpty()){
cur = stack.pop();
res.add(cur.val);
if(cur.left != null){stack.push(cur.left);}
if(cur.right != null){stack.push(cur.right);}
}
Collections.reverse(res);//注意这一点
return res;
}
}
3、二叉树层序遍历
107 层序遍历
【题目】
给定一个二叉树,返回其节点值自底向上的层序遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
【示例】
给定二叉树 [3,9,20,null,null,15,7]
3
/ \
9 20
/ \
15 7
返回其自底向上的层序遍历为:
[
[15,7],
[9,20],
[3]
]
【解答】
/**
* 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>> levelOrderBottom(TreeNode root) {
List<List<Integer>> resList = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();//因为需要频繁进行增加删除 故使用链表
if(root == null) return resList;
queue.offer(root);
while(!queue.isEmpty()){
int size = queue.size();
List<Integer> res = new ArrayList<>();
while(size>0){
TreeNode node = queue.poll();
res.add(node.val);
if(node.left != null){queue.offer(node.left);}
if(node.right != null){queue.offer(node.right);}
size--;
}
resList.add(res);
}
Collections.reverse(resList);//注意看题目,从底向上遍历
return resList;
}
}
199 二叉树的右视图
【题目】
给定一个二叉树的 根节点 root
,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
【示例】
输入: [1,2,3,null,5,null,4]
输出: [1,3,4]
【解答】
/**
* 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<Integer> rightSideView(TreeNode root) {
List<Integer> res = new ArrayList<>();
if(root == null) return res;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
int size = queue.size();
while(size > 0){
TreeNode node = queue.poll();
if(size == 1){//特殊判断 记录结果
res.add(node.val);
}
if(node.left != null){queue.offer(node.left);}
if(node.right != null){queue.offer(node.right);}
size--;
}
}
return res;
}
}
637 二叉树的层平均值
【题目】
给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。
【示例】
输入:
3
/ \
9 20
/ \
15 7
输出:[3, 14.5, 11]
解释:
第 0 层的平均值是 3 , 第1层是 14.5 , 第2层是 11 。因此返回 [3, 14.5, 11] 。
【解答】
/**
* 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<Double> averageOfLevels(TreeNode root) {
List<Double> res = new ArrayList<>();
if(root == null) return res;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
int size = queue.size();
int len = size;
Double sum = 0.0;
while(size>0){
TreeNode node = queue.poll();
sum = sum + node.val;
if(node.left != null){queue.offer(node.left);}
if(node.right != null){queue.offer(node.right);}
size--;
}
res.add(sum / len);//注意需要重新copy一个size 或者使用for循环
}
return res;
}
}
429 N叉树的层序遍历
【题目】
给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。
树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。
【示例】
输入:root = [1,null,3,2,4,null,5,6]
输出:[[1],[3,2,4],[5,6]]
输入:root = [1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]
输出:[[1],[2,3,4,5],[6,7,8,9,10],[11,12,13],[14]]
【解答】
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> children;//可以知道当前节点有几个孩子
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, List<Node> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
public List<List<Integer>> levelOrder(Node root) {
List<List<Integer>> resList = new ArrayList<>();
if(root == null) return resList;
Queue<Node> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
int size = queue.size();
List<Integer> res = new ArrayList<>();
while(size > 0){
Node node = queue.poll();
size--;//因为下面有一个continue 所以size--需要提前
res.add(node.val);
//需要考虑没有孩子的情况 没有孩子时 不能用size()
if(node.children == null || node.children.size() == 0){
continue;
}
int childLen = node.children.size();
for(Node child:node.children){//node.children是一个List
queue.offer(child);
}
}
resList.add(res);
}
return resList;
}
}
515 在每个树行找最大值
【题目】
给定一棵二叉树的根节点 root
,请找出该二叉树中每一层的最大值。
【示例】
输入: root = [1,3,2,5,3,null,9]
输出: [1,3,9]
解释:
1
/ \
3 2
/ \ \
5 3 9
【解答】
/**
* 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<Integer> largestValues(TreeNode root) {
List<Integer> res = new ArrayList<>();
if(root == null) return res;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
int size = queue.size();
int max = Integer.MIN_VALUE;
while(size>0){
TreeNode node = queue.poll();
max = Math.max(max,node.val);
if(node.left != null){queue.offer(node.left);}
if(node.right != null){queue.offer(node.right);}
size--;
}
res.add(max);
}
return res;
}
}
116 填充每个节点的下一右侧结点
【题目】
给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
【示例】
输入:root = [1,2,3,4,5,6,7]
输出:[1,#,2,3,#,4,5,6,7,#]
解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。序列化的输出按层序遍历排列,同一层节点由 next 指针连接,'#' 标志着每一层的结束。
【解答】
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node next;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, Node _left, Node _right, Node _next) {
val = _val;
left = _left;
right = _right;
next = _next;
}
};
*/
class Solution {
public Node connect(Node root) {
if(root == null) return root;
Queue<Node> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
int size = queue.size();
while(size > 0){
Node node = queue.poll();
size--;
if(size > 0){//这一层还有结点呢 也就是node不是最右边的结点 ----这里是关键------
node.next = queue.peek();
}
if(node.left != null){
queue.offer(node.left);
queue.offer(node.right);
}//其实只对最后一层 if才起作用
}
}
return root;
}
}
117 填充每个节点的下一右侧结点II
【题目】
给定一个二叉树
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
【示例】
输入:root = [1,2,3,4,5,null,7]
输出:[1,#,2,3,#,4,5,7,#]
解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。序列化输出按层序遍历顺序(由 next 指针连接),'#' 表示每层的末尾。
【解答】
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node next;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, Node _left, Node _right, Node _next) {
val = _val;
left = _left;
right = _right;
next = _next;
}
};
*/
class Solution {
public Node connect(Node root) {
if(root == null) return root;
Queue<Node> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
int size = queue.size();
while(size > 0){
Node node = queue.poll();
size--;
if(size > 0){//这一层还有结点呢 也就是node不是最右边的结点 ----这里是关键------
node.next = queue.peek();
}
if(node.left != null){queue.offer(node.left);}
if(node.right != null){queue.offer(node.right);}
}
}
return root;
}
}
104 二叉树的最大深度–递归待添加
【题目】
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
【示例】
3
/ \
9 20
/ \
15 7
返回最大深度3
【解答】
/**
* 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 maxDepth(TreeNode root) {
if(root == null)return 0;
int deep = 0;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
int size = queue.size();
while(size>0){
TreeNode node = queue.poll();
if(node.left != null){queue.offer(node.left);}
if(node.right != null){queue.offer(node.right);}
size--;
}
deep++;
}
return deep;
}
}
111 二叉树的最小深度–递归待添加
【题目】
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
**说明:**叶子节点是指没有子节点的节点。
【示例】
输入:root = [3,9,20,null,null,15,7]
输出: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 {
public int minDepth(TreeNode root) {
if(root == null)return 0;
int minDeep = 0;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
minDeep++;
int size = queue.size();
while(size > 0){
TreeNode node = queue.poll();
if(node.left != null){queue.offer(node.left);}
if(node.right != null){queue.offer(node.right);}
if(node.left == null && node.right == null){
return minDeep;
}
size--;
}
}
return minDeep;
}
}
4、二叉树的翻转
226 翻转二叉树
用多种方式翻转:递归,迭代,层序,前中后序
【题目】
翻转一棵二叉树。
【示例】
输入:
4
/ \
2 7
/ \ / \
1 3 6 9
输出:
4
/ \
7 2
/ \ / \
9 6 3 1
【解答】
深度优先遍历–递归
/**
* 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 invertTree(TreeNode root) {
dfs(root);
return root;//注意要有返回值
}
public void dfs(TreeNode node){
if(node == null) return;
swap(node);
dfs(node.left);
dfs(node.right);
}
public void swap(TreeNode node){
TreeNode temp = node.right;
node.right = node.left;
node.left = temp;
}
}
层序遍历
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root == null) return root;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
int size = queue.size();
while(size > 0){
TreeNode node = queue.poll();
size--;
swap(node);
if(node.left != null){queue.offer(node.left);}
if(node.right != null){queue.offer(node.right);}
}
}
return root;
}
}
前序遍历–统一模板
public TreeNode invertTree(TreeNode root) {
if(root == null) return root;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode node = stack.peek();
if(node != null){
stack.pop();
if(node.right != null){stack.push(node.right);}
if(node.left != null){stack.push(node.left);}
stack.push(node);
stack.push(null);
}else{//是中间结点
stack.pop();//其实是作为标记的null
TreeNode hnode = stack.pop();
swap(hnode);
}
}
return root;
}
5、N叉树的前后遍历
144 N叉树的前序遍历
【题目】
给定一个 N 叉树,返回其节点值的 前序遍历 。
N 叉树 在输入中按层序遍历进行序列化表示,每组子节点由空值 null
分隔(请参见示例)。
【示例】

输入:root = [1,null,3,2,4,null,5,6]
输出:[1,3,5,6,2,4]
【解答】
递归
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> children;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, List<Node> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
List<Integer> res = new ArrayList<>();
public List<Integer> preorder(Node root) {
dfs(root);
return res;
}
public void dfs(Node node){
if(node == null) return;//是null 或者 没有孩子 直接返回
res.add(node.val);
if(node.children.size() == 0)return;//是null 或者 没有孩子 直接返回
for(Node child:node.children){
dfs(child);
}
}
}
迭代
public List<Integer> preorder(Node root) {
List<Integer> res = new ArrayList<>();
if(root == null) return res;
Stack<Node> stack = new Stack<>();
stack.push(root);
while(!stack.isEmpty()){
Node node = stack.peek();
if(node != null){
stack.pop();
int size = node.children.size();
for(int i = size-1;i>=0;i--){//这里注意 逆序添加进stack 才是从左向右弹出
stack.push(node.children.get(i));
}
stack.push(node);
stack.push(null);
}else{
stack.pop();
res.add(stack.pop().val);
}
}
return res;
}
6、对称二叉树
101 对称的二叉树
【题目】
请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
【示例】
二叉树 [1,2,2,3,4,4,3] 是对称的。
1
/ \
2 2
/ \ / \
3 4 4 3
[1,2,2,null,3,null,3] 则不是镜像对称的:
1
/ \
2 2
\ \
3 3
【解答】
递归
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root == null) return true;
return compareTree(root.left,root.right);
}
public boolean compareTree(TreeNode left,TreeNode right){
//几种可返回结果的情况
if(left == null && right != null) return false;
if(left != null && right == null) return false;
if(left == null && right == null) return true;
if(left.val != right.val) return false;
boolean outside = compareTree(left.left,right.right);
boolean inside = compareTree(left.right,right.left);
return outside && inside;
}
}
迭代
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root == null) return true;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root.right);
queue.offer(root.left);
while(!queue.isEmpty()){
TreeNode left = queue.poll();
TreeNode right = queue.poll();
//四种可提前返回的情况
if(left == null && right != null) return false;
if(left != null && right == null) return false;
if(left == null && right == null) continue;//这里不是提前返回 而是不进行下面的
if(left.val != right.val) return false;
//注意push时的顺序 注意这里不要判断非空才push
queue.offer(left.left);
queue.offer(right.right);
queue.offer(left.right);
queue.offer(right.left);
}
return true;
}
}
100 相同的树
【题目】
给你两棵二叉树的根节点 p
和 q
,编写一个函数来检验这两棵树是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
【示例】
输入:p = [1,2,3], q = [1,2,3]
输出:true
输入:p = [1,2], q = [1,null,2]
输出:false
【解答】
递归
/**
* 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 isSameTree(TreeNode p, TreeNode q) {
if(p == null && q !=null) return false;
if(p !=null && q == null) return false;
if(p == null && q == null) return true;
if(p.val != q.val) return false;
boolean leftCheck = isSameTree(p.left,q.left);
boolean rightCheck = isSameTree(p.right,q.right);
return leftCheck && rightCheck;
}
}
迭代–栈
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if(p == null && q !=null) return false;
if(p !=null && q == null) return false;
if(p == null && q == null) return true;
if(p.val != q.val) return false;
//p q 根节点相同 且均不为空
Stack<TreeNode> stack = new Stack<>();
stack.push(p);
stack.push(q);
while(!stack.isEmpty()){
TreeNode qnode = stack.pop();
TreeNode pnode = stack.pop();
if(qnode == null && pnode == null) continue;
if(qnode != null && pnode == null) return false;
if(qnode == null && pnode != null) return false;
if(qnode.val != pnode.val) return false;
stack.push(pnode.left);
stack.push(qnode.left);
stack.push(pnode.right);
stack.push(qnode.right);
}
return true;
}
}
572 另一棵树的子树
【题目】
给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。
【示例】
输入:root = [3,4,5,1,2], subRoot = [4,1,2]
输出:true
输入:root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2]
输出:false
【解答】
/**
* 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 isSubtree(TreeNode root, TreeNode subRoot) {
Queue<TreeNode> queue = new LinkedList<>();//层序遍历root这棵树,每个节点作为根节点与subroot比较
if(root == null && subRoot == null)return true;
if(root == null && subRoot != null)return false;
queue.offer(root);
while(!queue.isEmpty()){
TreeNode node = queue.poll();
if(isSameTree(node,subRoot))return true;
if(node.left != null) queue.offer(node.left);
if(node.right != null) queue.offer(node.right);
}
return false;
}
//判断两个树是否相同
public boolean isSameTree(TreeNode p, TreeNode q) {
if(p == null && q !=null) return false;
if(p !=null && q == null) return false;
if(p == null && q == null) return true;
if(p.val != q.val) return false;
//p q 根节点相同 且均不为空
Stack<TreeNode> stack = new Stack<>();
stack.push(p);
stack.push(q);
while(!stack.isEmpty()){
TreeNode qnode = stack.pop();
TreeNode pnode = stack.pop();
if(qnode == null && pnode == null) continue;
if(qnode != null && pnode == null) return false;
if(qnode == null && pnode != null) return false;
if(qnode.val != pnode.val) return false;
stack.push(pnode.left);
stack.push(qnode.left);
stack.push(pnode.right);
stack.push(qnode.right);
}
return true;
}
}
7、完全二叉树的节点个数
222 完全二叉树的节点个数
【题目】
给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。
【示例】
输入:root = [1,2,3,4,5,6]
输出: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 {
int deep;
public int countNodes(TreeNode root) {
deep = 0;
if(root == null)return deep;
deep = getNum(root);
return deep;
}
public int getNum(TreeNode node){
if(node == null)return 0;
int lnum = getNum(node.left);
int rnum = getNum(node.right);
return lnum + rnum + 1;
}
}
迭代
class Solution {
public int countNodes(TreeNode root) {
if(root == null) return 0;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int deep = 0;
while(!queue.isEmpty()){
int size = queue.size();
deep = deep + size;
while(size > 0){
TreeNode node = queue.poll();
if(node.left != null) queue.offer(node.left);
if(node.right != null) queue.offer(node.right);
size--;
}
}
return deep;
}
}
8、平衡二叉树
110 平衡二叉树
【题目】
给定一个二叉树,判断它是否是高度平衡的二叉树。本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
【示例】

输入:root = [3,9,20,null,null,15,7]
输出:true

输入:root = [1,2,2,3,3,null,null,4,4]
输出:false
输入:root = []
输出:true
【解答】
/**
* 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 isBalanced(TreeNode root) {
if(root == null)return true;
int res = isBalancedSub(root);
if(res == -1){
return false;
}else{
return true;
}
}
public int isBalancedSub(TreeNode node){
if(node == null)return 0;
int nodeL = isBalancedSub(node.left);
if(nodeL == -1)return -1;
int nodeR = isBalancedSub(node.right);
if(nodeR == -1)return -1;
int subnode = Math.abs(nodeL-nodeR);
if(subnode > 1){
return -1;
}else{
return Math.max(nodeL,nodeR) + 1;//返回高度----注意!
}
}
}
9、二叉树的所有路径
257 二叉树的所有路径–迭代待添加
【题目】
给你一个二叉树的根节点 root
,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
叶子节点 是指没有子节点的节点。
【示例】
输入:root = [1,2,3,null,5]
输出:["1->2->5","1->3"]
【解答】
/**
* 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 {
List<String> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<String> binaryTreePaths(TreeNode root) {
if(root == null)return res;
getPaths(root);
return res;
}
public void getPaths(TreeNode node){
path.add(node.val);//--------前序遍历----中
if(node.left == null && node.right == null){//当前结点是叶子节点
StringBuilder pathBuilder = new StringBuilder();
for(int i = 0;i<path.size()-1;i++){
pathBuilder.append(""+path.get(i));
pathBuilder.append("->");
}
pathBuilder.append(""+path.get(path.size()-1));
res.add(pathBuilder.toString());
}
if(node.left != null){//--------前序遍历----左
getPaths(node.left);//node.left会被记录 且放入栈中
path.remove(path.size()-1);//node出栈----体现回溯
}
if(node.right != null){//--------前序遍历----右
getPaths(node.right);
path.remove(path.size()-1);
}
return;
}
}
10、左叶子之和
404 左叶子之和
【题目】
计算给定二叉树的所有左叶子之和。
【示例】
3
/ \
9 20
/ \
15 7
在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24
【解答】
/**
* 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 sumOfLeftLeaves(TreeNode root) {
if(root == null) return 0;//递归终止条件
int leftSum = sumOfLeftLeaves(root.left);
int rightSum = sumOfLeftLeaves(root.right);
int midSum = 0;
if((root.left!= null) && (root.left.left == null) && (root.left.right == null)){//是左叶子
midSum = root.left.val;
}
int sum = leftSum + rightSum + midSum;
return sum;
}
}
11、 找树左下角的值
513 找树左下角的值
【题目】
给定一个二叉树的 根节点 root
,请找出该二叉树的 最底层 最左边 节点的值。假设二叉树中至少有一个节点。
【示例】
输入: [1,2,3,4,null,5,6,null,null,7]
输出: 7
【解答】
迭代–层序
/**
* 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 findBottomLeftValue(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int res = 0;
while(!queue.isEmpty()){
int size = queue.size();
TreeNode node = queue.poll();
res = node.val;//会不断更新
if(node.left != null) queue.offer(node.left);
if(node.right != null) queue.offer(node.right);
size--;
while(size > 0){
node = queue.poll();
if(node.left != null) queue.offer(node.left);
if(node.right != null) queue.offer(node.right);
size--;
}
}
return res;
}
}
递归
需要找最后一行的最左边的值
class Solution {
int maxLen = Integer.MIN_VALUE;//记录深度 注意这里不能是0
int maxLeftValue = 0;//记录左边叶子结点的值
public int findBottomLeftValue(TreeNode root) {
traversal(root,0);
return maxLeftValue;
}
public void traversal(TreeNode node,int len){
if(node.left == null && node.right == null){//是叶子节点
if(maxLen<len){//到新的一层的叶子结点
maxLen = len;
maxLeftValue = node.val;
}
return;
}
//前序遍历
if (node.left != null){
len++;//递归到下一层----node.left
traversal(node.left,len);//回溯到上一层-----node
len--;
}
if (node.right != null){
len++;
traversal(node.right,len);
len--;
}
return;
}
}
12、路径总和
112 路径总和–迭代待添加
【题目】
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所 有节点值相加等于目标和 targetSum 。叶子节点 是指没有子节点的节点。
【示例】

输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
【解答】
/**
* 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 hasPathSum(TreeNode root, int targetSum) {
if(root == null) return false;
return traversal(root,targetSum-root.val);
}
public boolean traversal(TreeNode node,int count){//count在递归时已经减去node对应的值了
if(node.left == null && node.right == null && count == 0) return true;
if(node.left == null && node.right == null) return false;
//---前序遍历
if(node.left != null){
count = count - node.left.val;
if(traversal(node.left,count)) return true;//若找到 则及时返回--否则报错
count = count + node.left.val;//回溯
}
if(node.right != null){
count = count - node.right.val;
if(traversal(node.right,count)) return true;//若找到 则及时返回
count = count + node.right.val;
}
return false;
}
}
113 路径总和II
【题目】
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
【示例】

输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]
【解答】
递归
/**
* 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 {
//此题需要遍历一棵树的所有路径且不需要处理返回值 故不需要返回值
List<List<Integer>> resList = new ArrayList<>();
List<Integer> paths = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
if(root == null) return resList;
paths.add(root.val);
traversal(root,targetSum-root.val);
return resList;
}
public void traversal(TreeNode node,int count){
if(node.left == null && node.right == null && count == 0){//找到一条满足路径
resList.add(new ArrayList(paths));//--------这里注意,直接add(paths)是错误的
return ;
}
if(node.left == null && node.right == null) return ;
//前序遍历
if(node.left != null){
paths.add(node.left.val);
count = count - node.left.val;
traversal(node.left,count);
count = count + node.left.val;
paths.remove(paths.size()-1);
}
if(node.right != null){
paths.add(node.right.val);
count = count - node.right.val;
traversal(node.right,count);
count = count + node.right.val;
paths.remove(paths.size()-1);
}
return;
}
}
437 路径总和III
【题目】
给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。路径 不需要 从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
【示例】
输入:root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
输出:3
解释:和等于 8 的路径有 3 条,如图所示。
【解答】
层序–每个节点递归
/**
* 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 resNum = 0;
public int pathSum(TreeNode root, int targetSum) {
if(root == null)return 0;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
int size = queue.size();
while(size > 0){
TreeNode node = queue.poll();
traversal(node,targetSum-node.val);
if(node.left != null) queue.offer(node.left);
if(node.right != null) queue.offer(node.right);
size--;
}
}
return resNum;
}
public void traversal(TreeNode node,int count){
if(count == 0){//找到一条满足路径 因为不需在叶子结点结束
resNum++;
//return ; 也许从此节点到叶子结点的路径加起来为0 呢
}
if(node.left == null && node.right == null) return ;
//前序遍历
if(node.left != null){
count = count - node.left.val;
traversal(node.left,count);
count = count + node.left.val;
}
if(node.right != null){
count = count - node.right.val;
traversal(node.right,count);
count = count + node.right.val;
}
return;
}
}
13、构造二叉树
106 从中序与后序遍历序列构造二叉树
【题目】
根据一棵树的中序遍历与后序遍历构造二叉树。
**注意:**你可以假设树中没有重复的元素。
【示例】
中序遍历 inorder = [9,3,15,20,7]
后序遍历 postorder = [9,15,7,20,3]
3
/ \
9 20
/ \
15 7
【解答】
/**
* 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 buildTree(int[] inorder, int[] postorder) {
if(inorder.length == 0)return null;
int len = postorder.length;
TreeNode root = new TreeNode(postorder[len-1]);//post最后一个元为根节点
//找根节点在inorder中的下标
int rootIndex = 0;
for(rootIndex = 0;rootIndex<len;rootIndex++){
if(inorder[rootIndex] == postorder[len-1]){
break;
}
}
//根据rootIndex将inorder划分为LeftInOrder RightInOrder
int[] LeftInOrder = copyArray(inorder,0,rootIndex);
int[] RightInOrder = copyArray(inorder,rootIndex+1,len);
//根据LeftInOrder和RightInOrder的长度划分PostOrder
int[] LeftPostOrder = copyArray(postorder,0,LeftInOrder.length);
int[] RightPostOrder = copyArray(postorder,LeftInOrder.length,len-1);
root.left = buildTree(LeftInOrder,LeftPostOrder);
root.right = buildTree(RightInOrder,RightPostOrder);
return root;
}
public int[] copyArray(int[] oldArray,int start,int end){//左闭右开原则
int len = end - start;
int[] resArray = new int[len];
for(int i = 0;i<len;i++){
resArray[i] = oldArray[start+i];
}
return resArray;
}
}
105 从前序与中序遍历序列构造二叉树
【题目】
给定一棵树的前序遍历 preorder
与中序遍历 inorder
。请构造二叉树并返回其根节点。
【示例】
Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]
【解答】
/**
* 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 buildTree(int[] preorder, int[] inorder) {
if(inorder.length == 0)return null;
int len = preorder.length;
TreeNode root = new TreeNode(preorder[0]);//preOrder第一个元素为根节点
//找根节点在inorder中的下标
int rootIndex = 0;
for(rootIndex = 0;rootIndex<len;rootIndex++){
if(inorder[rootIndex] == preorder[0]){
break;
}
}
//根据rootIndex将inorder划分为LeftInOrder RightInOrder
int[] LeftInOrder = copyArray(inorder,0,rootIndex);
int[] RightInOrder = copyArray(inorder,rootIndex+1,len);
//根据LeftInOrder和RightInOrder的长度划分PreOrder
int[] LeftPreOrder = copyArray(preorder,1,1+LeftInOrder.length);
int[] RightPreOrder = copyArray(preorder,1+LeftInOrder.length,len);
root.left = buildTree(LeftPreOrder,LeftInOrder);
root.right = buildTree(RightPreOrder,RightInOrder);
return root;
}
public int[] copyArray(int[] oldArray,int start,int end){//左闭右开原则
int len = end - start;
int[] resArray = new int[len];
for(int i = 0;i<len;i++){
resArray[i] = oldArray[start+i];
}
return resArray;
}
}
654 最大二叉树
【题目】
给定一个不含重复元素的整数数组 nums 。一个以此数组直接递归构建的 最大二叉树 定义如下:
·二叉树的根是数组 nums 中的最大元素。
·左子树是通过数组中 最大值左边部分 递归构造出的最大二叉树。
·右子树是通过数组中 最大值右边部分 递归构造出的最大二叉树。
返回有给定数组 nums 构建的 最大二叉树 。
【示例】
输入:nums = [3,2,1,6,0,5]
输出:[6,3,5,null,2,0,null,null,1]
【解答】
优化了一下代码:不用每次创建新的数组
/**
* 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 constructMaximumBinaryTree(int[] nums) {
return constructMaximumBinaryTree1(nums,0,nums.length);
}
public TreeNode constructMaximumBinaryTree1(int[] nums,int start,int end){//不用重新构造,直接在nums上操作
if(end - start == 0) return null;
int rooIndex = getMaxIndex(nums,start,end);
TreeNode root = new TreeNode(nums[rooIndex]);
root.left = constructMaximumBinaryTree1(nums,start,rooIndex);
root.right = constructMaximumBinaryTree1(nums,rooIndex+1,end);
return root;
}
//在nums[start,end)上找最大值对应的索引
public int getMaxIndex(int[] nums,int start,int end){
int maxValue = Integer.MIN_VALUE;
int maxIndex = start;
for(int i = start;i<end;i++){
if(nums[i]>maxValue){//更新
maxValue = nums[i];
maxIndex = i;
}
}
return maxIndex;
}
}
14、合并二叉树
617 合并二叉树
【题目】
给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。
你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不 为 NULL 的节点将直接作为新二叉树的节点。
【示例】
输入:
Tree 1 Tree 2
1 2
/ \ / \
3 2 1 3
/ \ \
5 4 7
输出:
合并后的树:
3
/ \
4 5
/ \ \
5 4 7
【解答】
/**
* 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;
if(root1 != null && root2 != null){
root1.val = root1.val + root2.val;
}
root1.left = mergeTrees(root1.left,root2.left);
root1.right = mergeTrees(root1.right,root2.right);
return root1;
}
}
15、二叉搜索树中的搜索
700 二叉搜索树中的搜索
【题目】
给定二叉搜索树(BST)的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节 点不存在,则返回 NULL。
【示例】
给定二叉搜索树:
4
/ \
2 7
/ \
1 3
和值: 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 {
public TreeNode searchBST(TreeNode root, int val) {
if(root == null)return null;
if(root.val == val) return root;
if(root.val < val) return searchBST(root.right,val);
return searchBST(root.left,val);
}
}
98 验证二叉搜索树
【题目】
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
·节点的左子树只包含 小于 当前节点的数。
·节点的右子树只包含 大于 当前节点的数。
·所有左子树和右子树自身必须也是二叉搜索树。
【示例】
输入:root = [2,1,3]
输出:true
【解答】
二叉搜索树的中序遍历序列是单调递增的
/**
* 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 {
List<Integer> resList = new ArrayList<>();
public boolean isValidBST(TreeNode root) {
inTrace(root);
for(int i = 0;i<resList.size()-1;i++){
if(resList.get(i)>=resList.get(i+1)){
return false;
}
}
return true;
}
public void inTrace(TreeNode node){
if(node == null) return;
inTrace(node.left);
resList.add(node.val);
inTrace(node.right);
}
}
中序遍历–可以提前返回–用一个max
class Solution {
TreeNode max;//或者说是pre
public boolean isValidBST(TreeNode root) {
if(root == null) return true;
boolean leftValid = isValidBST(root.left);
if(!leftValid) return false;//直接返回
if(max!= null && max.val>=root.val) return false;//更新max确保是递增的
max = root;//更新max
boolean rightValid = isValidBST(root.right);
return rightValid;
}
}
16、二叉搜索树的最小绝对差
530 二叉搜索树的最小绝对差
【题目】
给你一个二叉搜索树的根节点 root
,返回 树中任意两不同节点值之间的最小差值 。
差值是一个正数,其数值等于两值之差的绝对值。
【示例】

输入:root = [4,2,6,1,3]
输出:1
【解答】
中序遍历,然后找最小值;或者用一个pre,在遍历中更新最小值
/**
* 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 {
TreeNode pre = null;
int minValue = Integer.MAX_VALUE;
public int getMinimumDifference(TreeNode root) {
traversal(root);
return minValue;
}
public void traversal(TreeNode cur){
if(cur == null)return ;
traversal(cur.left);//---------左
if(pre != null){//----------中
minValue = Math.min(minValue,cur.val - pre.val);
}
pre = cur;
traversal(cur.right);//-----------右
return;
}
}
17、二叉搜索树的众数
501 二叉搜索树的众数
【题目】
给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。假定 BST 有如下定义:
·结点左子树中所含结点的值小于等于当前结点的值
·结点右子树中所含结点的值大于等于当前结点的值
·左子树和右子树都是二叉搜索树
【示例】
1
\
2
/
2
输出[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 {
List<Integer> resList = new ArrayList<>();
TreeNode pre = null;
int count = 0,maxCount = 1;
public int[] findMode(TreeNode root) {
traversal(root);
int[] res = new int[resList.size()];//ArrayList转为int数组 没有现成函数
for(int i = 0;i<resList.size();i++){
res[i] = resList.get(i);
}
return res;
}
public void traversal(TreeNode cur){
if(cur == null)return ;
traversal(cur.left);//--------左
if(pre != null){
if(pre.val == cur.val){//连续出现多次--有成为众数的可能
count++;
}else{//遇到不同的值
count = 1;
}
if(count == maxCount){
resList.add(cur.val);
}else if(count > maxCount){
maxCount = count;
resList.clear();
resList.add(cur.val);
}//--<的时候不用添加
}else{//第一个结点--只有这种情况
count = 1;
resList.add(cur.val);
}
pre = cur;
traversal(cur.right);//----------右
return ;
}
}
18、最近的公共祖先
剑指68II 二叉树的最近公共祖先
【题目】
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。(一个节点也可以是它自己的祖先)。
【示例】
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
【解答】
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == p||root ==q||root == null) return root;
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
if(left != null && right != null)return root;
if(left != null) return left;
return right;
}
}
剑指I 二叉搜索树中的最近公共祖先
【题目】
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
【示例】
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6
解释: 节点 2 和节点 8 的最近公共祖先是 6。
【解答】
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
return traversal(root,p,q);
}
public TreeNode traversal(TreeNode node,TreeNode p,TreeNode q){
if(node == null) return null;
if(node.val > p.val && node.val > q.val) {
return traversal(node.left,p,q);
}
if(node.val < p.val && node.val < q.val){
return traversal(node.right,p,q);
}
//否则当前节点就是分叉点
return node;
}
}
19、删除二叉树的结点
450 删除搜索二叉树的结点
【题目】
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉 搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
·首先找到需要删除的节点;
·如果找到了,删除它。
【示例】


【解答】
/**
* 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 deleteNode(TreeNode root, int key) {
if(root == null)return null;
if(root.val > key){
root.left = deleteNode(root.left,key);//left这里来接收,当前结点保留
}
if(root.val < key){
root.right = deleteNode(root.right,key);
}
if(root.val == key){
//1 待删除结点没有孩子结点--即待删除结点是叶子结点,则用null来代替自己
if(root.left == null && root.right == null) return null;
//2 待删除结点只有左孩子,则让左孩子代替自己-------注意判断条件要写全 不能只写root.left != null
if(root.left != null && root.right == null) return root.left;
//3 待删除结点只有右孩子,则让右孩子代替自己
if(root.left == null && root.right != null) return root.right;
//4 待删除结点有左右孩子,则两种方式(A 找中序遍历的前驱,前驱.right = 后继
// B 找中序遍历的后继,后继.left = 前驱)
TreeNode prenode = root.left;
while(prenode.right != null){
prenode = prenode.right;
}//prenode.right = null
prenode.right = root.right;
return root.left;
}
return root;
}
}
669 修剪二叉搜索树
【题目】
给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树不应该改变保留在树中的元素的相对结构(即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证 明,存在唯一的答案。
所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。
【示例】

输入:root = [1,0,2], low = 1, high = 2
输出:[1,null,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 {
public TreeNode trimBST(TreeNode root, int low, int high) {
if(root == null) return null;
if(root.val > high){
return trimBST(root.left,low,high);//相当于把当前结点删除
}
if(root.val < low){
return trimBST(root.right,low,high);
}
//root.val 在[low,high]之间
root.left = trimBST(root.left,low,high);//接收返回值
root.right = trimBST(root.right,low,high);
return root;
}
}
701 二叉搜索树的插入
【题目】
给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 , 新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
【示例】
输入:root = [4,2,7,1,3], val = 5
输出:[4,2,7,1,3,5]
解释:另一个满足题目要求可以通过的树是:

【解答】
/**
* 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 insertIntoBST(TreeNode root, int val) {
if(root == null){//找到叶子节点 此时可以插入了
return new TreeNode(val);
}
if(root.val > val){//需要有接收值的
root.left = insertIntoBST(root.left,val);
}
if(root.val < val){
root.right = insertIntoBST(root.right,val);
}
return root;
}
}
20、将有序数组转换为二叉搜索树
【题目】
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
【示例】

输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案:

【解答】
/**
* 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 sortedArrayToBST(int[] nums) {
return construct(nums,0,nums.length);
}
public TreeNode construct(int[] nums,int start,int end){//采用左闭右开 这样就不用+1
int len = end - start;
if(len == 0) return null;
int midIndex = start + len/2;
TreeNode root = new TreeNode(nums[midIndex]);
root.left = construct(nums,start,midIndex);
root.right = construct(nums,midIndex+1,end);
return root;
}
}
21、把二叉搜索树转化为累加树
538 把二叉搜索树转化为累加树
【题目】
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等 于原树中大于或等于 node.val 的值之和。
提醒一下,二叉搜索树满足下列约束条件:
·节点的左子树仅包含键 小于 节点键的节点。
·节点的右子树仅包含键 大于 节点键的节点。
·左右子树也必须是二叉搜索树。
【示例】

输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8]
输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8]
【解答】
/**
* 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 count = 0;
public TreeNode convertBST(TreeNode root) {
traversal(root);
return root;
}
public void traversal(TreeNode root){
if(root == null)return;
traversal(root.right);
root.val = root.val + count;
count = root.val;
traversal(root.left);
}
}
·回溯算法
模板
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
【注】
1、startIndex是否需要:每次选取元素的时候需不需要从头开始选(允不允许元素重复选择)
2、如果结果集只记录叶子节点,则需要判断是否可以加入结果集;如果需要记录每个结点的值,则进回溯函数后可以直接add
3、可以用used记录这个点是否被访问过,同一行不能被重复访问:used[]=fasle,同一个树枝不能被重复访问:used[]=true
4、回溯函数是void还是Boolean,看需不需要返回值,如果需要提前判断的考虑用Boolean
5、记得回溯复原
6、看看能不能在for循环里面剪枝
7、需不需要在将数组排序,作为去重

1、组合问题
77 组合问题
【题目】
给定两个整数 n
和 k
,返回范围 [1, n]
中所有可能的 k
个数的组合。
你可以按 任何顺序 返回答案。说明是组合问题
【示例】
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
【解答】
class Solution {
List<List<Integer>> resList = new ArrayList<>();//存储所有结果
List<Integer> path = new ArrayList<>();//存放一条路径
public List<List<Integer>> combine(int n, int k) {
backtracking(n,k,1);
return resList;
}
public void backtracking(int n,int k,int startIndex){
if(path.size() == k){//找到了k个数值
//resList.add(path);
// System.out.println(path.toString());//这里确实是有数值的
resList.add(new ArrayList<>(path));
return;
}
//for(int i = startIndex;i<=n;i++){
for(int i = startIndex;i<=n-(k-path.size())+1;i++){//根据长度做一个剪枝
path.add(i);
backtracking(n,k,i+1);//递归
path.remove(path.size()-1);//回溯
}
return;
}
}
216 组合总和
【题目】
找出所有相加之和为 n 的 k* 个数的组合**。***组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
- 所有数字都是正整数。
- 解集不能包含重复的组合
【示例】
输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
【解答】
class Solution {
List<List<Integer>> resList = new ArrayList<>();
List<Integer> path = new ArrayList<>();
int sum = 0;
public List<List<Integer>> combinationSum3(int k, int n) {
backTracking(k,n,1);
return resList;
}
public void backTracking(int k,int n,int startIndex){
if(path.size() == k&&sum == n){
resList.add(new ArrayList<>(path));
return ;
}
for(int i = startIndex;i<=9-(k-path.size())+1&&i<=n-sum;i++){//剪枝
path.add(i);
sum = sum + i;
backTracking(k,n,i+1);
sum = sum -i;//回溯
path.remove(path.size()-1);//回溯
}
}
}
17 电话号码的组合
【题目】
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。、
【示例】
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
输入:digits = ""
输出:[]
【解答】
class Solution {
String[] letterMap ={
"", // 0
"", // 1
"abc", // 2
"def", // 3
"ghi", // 4
"jkl", // 5
"mno", // 6
"pqrs", // 7
"tuv", // 8
"wxyz" // 9
};
List<String> resList = new ArrayList<>();
StringBuilder sb = new StringBuilder();//记录每次的字符串
int index = 0;
//int resIndex = 0;
public List<String> letterCombinations(String digits) {
if(digits.length() == 0) return resList;
//resIndex = digits.length();
backtracking(0,digits);
return resList;
}
public void backtracking(int index,String digits){
if(index == digits.length()){
String res = sb.toString();
resList.add(res);
return;
}
int dight = digits.charAt(index) - '0';//遍历的第index对应的数字
String letter = letterMap[dight];//该数字对应的字符串映射
//其实是每一层要做的事情
for(int i = 0;i<letter.length();i++){
sb.append(letter.charAt(i));
index++;
backtracking(index,digits);
index--;
sb.delete(sb.length()-1,sb.length());
}
}
}
39 组合总和
【题目】
给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。
candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是唯一的。
对于给定的输入,保证和为 target 的唯一组合数少于 150 个。
【示例】
输入: candidates = [2,3,6,7], target = 7
输出: [[7],[2,2,3]]
【解答】
class Solution {
List<List<Integer>> resList = new ArrayList<>();
List<Integer> path = new ArrayList<>();
int sum = 0;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);//通过排序剪枝
backtracking(candidates,target,0);
return resList;
}
public void backtracking(int[] candidates,int target,int startIndex){
if(sum == target){
resList.add(new ArrayList<>(path));
return;
}
for(int i = startIndex;i<candidates.length&&sum+candidates[i]<=target;i++){
sum = sum + candidates[i];
path.add(candidates[i]);
backtracking(candidates,target,i);
sum = sum - candidates[i];
path.remove(path.size()-1);//回溯
}
}
}
40 组合总和II
【题目】
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
注意:解集不能包含重复的组合。
【示例】
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]
【解答】
class Solution {
List<List<Integer>> resList = new ArrayList<>();
List<Integer> path = new ArrayList<>();
int sum = 0;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);//通过排序剪枝
backtracking(candidates,target,0);
return resList;
}
public void backtracking(int[] candidates,int target,int startIndex){
if(sum == target){
resList.add(new ArrayList<>(path));
return;
}
for(int i = startIndex;i<candidates.length&&sum+candidates[i]<=target;i++){
if(i>startIndex && candidates[i] == candidates[i-1]){///最关键的
continue;
}
sum = sum + candidates[i];
path.add(candidates[i]);
backtracking(candidates,target,i+1);
sum = sum - candidates[i];
path.remove(path.size()-1);//回溯
}
}
}
2、分隔问题
131 分隔回文串
【题目】
给你一个字符串 s
,请你将 s
分割成一些子串,使每个子串都是 回文串 。返回 s
所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
【示例】
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]
【解答】
class Solution {
List<List<String>> resList = new ArrayList<>();
List<String> path = new ArrayList<>();
public List<List<String>> partition(String s) {
backtracking(s,0);
return resList;
}
public void backtracking(String s,int startIndex){//startIndex为分割线
if(startIndex >= s.length()){
resList.add(new ArrayList<>(path));
return;
}
for(int i = startIndex;i<s.length();i++){
if(isPartition(s,startIndex,i)){
path.add(s.substring(startIndex,i+1));
}else{F
continue;
}
backtracking(s,i+1);
path.remove(path.size()-1);//回溯
}
}
public boolean isPartition(String s,int left,int right){
while(left < right){
if(s.charAt(left)!=s.charAt(right)){
return false;
}
left++;
right--;
}
return true;
}
}
93.复原IP地址
【题目】
【示例】
【解答】
验证IP地址
import java.util.*;
public class Solution {
/**
* 验证IP地址
* @param IP string字符串 一个IP地址字符串
* @return string字符串
*/
public String solve (String IP) {
// write code here
if(IP.contains(".")&&isIPV4(IP)){
return "IPv4";
}else if (IP.contains(":")&&isIPV6(IP)){
return "IPv6";
}
return "Neither";
}
public static boolean isIPV4(String address){
//是数字
//四组
//是数字
//在【0,255】
String[] addStrs = address.split("\\.",-1);
if(addStrs.length != 4){
return false;
}
for (int i = 0;i< addStrs.length;i++){
if (addStrs[i].startsWith("0")){//0 开头
return false;
}
String str = addStrs[i];
for (int j = 0; j < str.length(); j++) {//判断是不是都是数字
if(str.charAt(j)>'9'||str.charAt(j)<'0'){
return false;
}
}
int num = Integer.valueOf(str);
if(num>255||num<0){
return false;
}
}
return true;
}
public static boolean isIPV6(String address){
String[] addStrs = address.split("\\:",-1);
if (addStrs.length != 8){
return false;
}
for (int i = 0; i < addStrs.length; i++) {
if (addStrs[i].length()>4 || addStrs[i].length()<1){
return false;
}
for(int j = 0;j<addStrs[i].length();j++){
char c = addStrs[i].charAt(j);
if(!( (c>='0'&&c<='9') || (c>='a'&&c<='f') || (c>='A'&&c<='F') ) ){
return false;
}
}
//可用下面代替
try {
int val = Integer.parseInt(addStrs[i],16);
}catch (NumberFormatException numberFormatException){
return false;
}
}
return true;
}
}
3、子集问题
78 子集
【题目】
给你一个整数数组 nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
【示例】

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
【解答】
class Solution {
List<List<Integer>> resList = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
backtracking(nums,0);
return resList;
}
public void backtracking(int[] nums,int startIndex){
把遍历的结点都记录下来
resList.add(new ArrayList(path));
if(startIndex >= nums.length){
return;
}
for(int i = startIndex;i<nums.length;i++){
path.add(nums[i]);
backtracking(nums,i+1);
path.remove(path.size()-1);
}
}
}
90、子集II
【题目】
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
【示例】
输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
【解答】
class Solution {
List<List<Integer>> resList = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);//注意提前排序
backtracking(nums,0);
return resList;
}
public void backtracking(int[] nums,int startIndex){
resList.add(new ArrayList<>(path));
for(int i = startIndex;i<nums.length;i++){
if(i>startIndex&&nums[i] == nums[i-1]){//同一层不能重复选同样的元素 可以不用used
continue;
}
path.add(nums[i]);
backtracking(nums,i+1);//i+1 表示同一元素不能重复选
path.remove(path.size()-1);
}
}
}
491 递增子序列
【题目】
给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返 回答案。数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
num[i]∈[-100,100]
【示例】
输入:nums = [4,4,3,2,1]
输出:[[4,4]]
【解答】
class Solution {
List<List<Integer>> resList = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> findSubsequences(int[] nums) {
backtracking(nums,0);
return resList;
}
public void backtracking(int[] nums,int startIndex){
if(path.size()>=2){
resList.add(new ArrayList<>(path));
}
int[] used = new int[201];
for(int i = startIndex;i<nums.length;i++){
if(path.size()>0&&path.get(path.size()-1)>nums[i]
|| used[nums[i]+100] != 0){//不是递增 或 这一层已经使用过了
continue;
}
path.add(nums[i]);
used[nums[i]+100] = 1;
backtracking(nums,i+1);
path.remove(path.size()-1);
}
}
}
4、排列问题
46 全排列
【题目】
给定一个不含重复数字的数组 nums
,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
【示例】

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
【解答】
class Solution {
List<List<Integer>> resList = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
boolean[] used = new boolean[nums.length];
backtracking(nums,used);
return resList;
}
public void backtracking(int[] nums,boolean[] used){
if(path.size() == nums.length){
resList.add(new ArrayList<>(path));
return ;
}
for(int i = 0;i<nums.length;i++){//对每一层来说
if(used[i]){//同一层不能选一样的
continue;
}
used[i] = true;
path.add(nums[i]);
backtracking(nums,used);
used[i] = false;
path.remove(path.size()-1);
}
}
}
47 全排列II
【题目】
给定一个可包含重复数字的序列 nums
,按任意顺序 返回所有不重复的全排列。
【示例】
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
【解答】
注意同一层不能重复选取的判定规则
class Solution {
List<List<Integer>> resList = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
Arrays.sort(nums);
boolean[] used = new boolean[nums.length];
backtracking(nums,used);
return resList;
}
public void backtracking(int[] nums,boolean[] used){
if(path.size() == nums.length){
resList.add(new ArrayList<>(path));
return;
}
for(int i = 0;i<nums.length;i++){
//used[i-1]==true 一个树枝上用过,可以,排列中数字可以是相同的
//used[i-1]==false 一层中用过,同一层不能重复选取
if(i>0&&nums[i]==nums[i-1]&&!used[i-1]){
continue;
}
if(!used[i]){
used[i] = true;
path.add(nums[i]);
backtracking(nums,used);
used[i] = false;
path.remove(path.size()-1);
}
}
}
}
5、规划问题
【题目】
给你一份航线列表 tickets ,其中 tickets[i] = [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。
所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。如果存在多种有效的行程,请你按 字典排序返回最小的行程组合。
例如,行程 [“JFK”, “LGA”] 与 [“JFK”, “LGB”] 相比就更小,排序更靠前。
假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。
【示例】

输入:tickets = [["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]
输出:["JFK","ATL","JFK","SFO","ATL","SFO"]
解释:另一种有效的行程是 ["JFK","SFO","ATL","JFK","ATL","SFO"] ,但是它字典排序更大更靠后。
【解答】
字母升序的Map
class Solution {
List<String> resList = new ArrayList<>();
Map<String,Map<String,Integer>> map = new HashMap<>();
public List<String> findItinerary(List<List<String>> tickets) {
//构造Map 记录[A,B]有几张车票,因为需要都用了
for(List<String> ticket:tickets){//每一个ticket都是一个[A,B]
Map<String,Integer> t = new HashMap<>();
//t.merge(ticket.get(1),1,Integer::sum);//没有按字典序排列
// map.put(ticket.get(0),t);
if(map.containsKey(ticket.get(0))){//出现过[A,...]
t = map.get(ticket.get(0));
t.put(ticket.get(1),t.getOrDefault(ticket.get(1),0)+1);
}else{
t = new TreeMap<>();//体现是按字母序升序的map
t.put(ticket.get(1),1);
}
map.put(ticket.get(0),t);
}
resList.add("JFK");
backtracking(tickets.size());
return resList;
}
public boolean backtracking(int ticketNum){
if(resList.size() == ticketNum + 1){//找到一个合适的解,就返回为真
return true;
}
String from = resList.get(resList.size()-1);//以此找下一可达的地点
if(map.containsKey(from)){
for(Map.Entry<String,Integer> target:map.get(from).entrySet()){
int count = target.getValue();
if(count>0){//当前from还有车票
resList.add(target.getKey());
target.setValue(count-1);
if(backtracking(ticketNum)) return true;
resList.remove(resList.size()-1);//回溯
target.setValue(count);//不要忘这里的回溯
}
}
}
return false;//注意这里返回false
}
}
6、棋盘问题
N 皇后问题
【题目】
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
【示例】

输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。
【解答】
class Solution {
List<List<String>> resList = new ArrayList<>();
public List<List<String>> solveNQueens(int n) {
char[][] chessboard = new char[n][n];
for(int i = 0;i<n;i++){
for(int j = 0;j<n;j++){
chessboard[i][j] = '.';
}
}
backTracking(chessboard,n,0);
return resList;
}
public void backTracking(char[][] chessboard,int n,int row){
//row为参数 为了判断终止条件;判断是哪一行
if(row == n){
resList.add(Array2List(chessboard));
return;
}
for(int j = 0;j<n;j++){
if(isVaild(row,j,chessboard)){//可以在这点
chessboard[row][j] = 'Q';
backTracking(chessboard,n,row+1);
chessboard[row][j] = '.';//回溯
}
}
}
public List<String> Array2List(char[][] chessboard){
List<String> list = new ArrayList<>();
for(char[] ch:chessboard){
list.add(String.copyValueOf(ch));
}
return list;
}
public boolean isVaild(int row,int col,char[][] chessboard){
for(int i = 0;i<row;i++){//同一列
if(chessboard[i][col] == 'Q'){
return false;
}
}
for(int i = row-1,j = col-1;i>=0&&j>=0;i--,j--){//左斜上方
if(chessboard[i][j] == 'Q'){
return false;
}
}
for(int i = row-1,j = col+1;i>=0&&j<chessboard[0].length;i--,j++){//右斜上方
if(chessboard[i][j] == 'Q'){
return false;
}
}
return true;
}
}
37 解数独
【题目】
编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
·数字 1-9 在每一行只能出现一次。
·数字 1-9 在每一列只能出现一次。
·数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 ‘.’ 表示。
【示例】


【解答】
class Solution {
public void solveSudoku(char[][] board) {
backtracking(board);
}
public boolean backtracking(char[][] board){
for(int i = 0;i<9;i++){
for(int j = 0;j<9;j++){
if(board[i][j] != '.'){continue;}
for(char k = '1';k <= '9';k++){
if(isVaild(i,j,k,board)){
board[i][j] = k;
if(backtracking(board)) return true;
board[i][j] = '.';//回溯
}
}
//这里不返回的话,说明这一行没有找到满足条件的k
return false;
}
}
return true;
}
public boolean isVaild(int row,int col,char k,char[][] board){//k能否放在[row,col]处
for(int i = 0;i<9;i++){//同行
if(board[row][i] == k){
return false;
}
}
for(int i = 0;i<9;i++){//同列
if(board[i][col] == k){
return false;
}
}
int startX = (row/3)*3;
int startY = (col/3)*3;
for(int i = startX;i<startX+3;i++){
for(int j = startY;j<startY+3;j++){
if(board[i][j] == k){
return false;
}
}
}
return true;
}
}
·贪心算法
解题步骤
贪心算法一般分为如下四步:
- 将问题分解为若干个子问题
- 找出适合的贪心策略
- 求解每一个子问题的最优解
- 将局部最优解堆叠成全局最优解
题型

简单题
455 分发饼干
【题目】
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数 值。
【局部最优:大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个】
**【全局最优:喂饱尽可能多的小孩。】
【示例】

输入: g = [1,2,3], s = [1,1]
输出: 1
解释:
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。
【解答】
class Solution {
public int findContentChildren(int[] g, int[] s) {
//g是胃口 s是饼干
int result = 0;
int index = s.length-1;
Arrays.sort(g);
Arrays.sort(s);
for(int i = g.length -1;i>=0;i--){
if(index>=0&&s[index]>=g[i]){
index--;
result++;
}
}
return result;
}
}
376 摆动序列—动态规划版本未实现
【题目】
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。
例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3) 是正负交替出现的。
相反,[1, 4, 7, 2, 5] 和 [1, 7, 4, 5, 5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。
给你一个整数数组 nums ,返回 nums 中作为 摆动序列 的 最长子序列的长度 。
【局部最优:删除单调坡度上的节点(不包括单调坡度两端的节点),那么这个坡度就可以有两个局部峰值。】
【整体最优:整个序列有最多的局部峰值,从而达到最长摆动序列。】

【示例】
输入:nums = [1,7,4,9,2,5]
输出:6
解释:整个序列均为摆动序列,各元素之间的差值为 (6, -3, 5, -7, 3) 。
输入:nums = [1,17,5,10,13,15,10,5,16,8]
输出:7
解释:这个序列包含几个长度为 7 摆动序列。
其中一个是 [1, 17, 10, 13, 10, 16, 8] ,各元素之间的差值为 (16, -7, 3, -3, 6, -8) 。
【解答】
class Solution {
public int wiggleMaxLength(int[] nums) {
int result = 1;//默认0的左边有一个值,这样的话就不用单独考虑最左边和最右边的值了
int preDiff = 0;//[i-1,i]
int curDiff = 0;//[i,i+1]
for(int i = 0;i<nums.length-1;i++){
curDiff = nums[i+1] - nums[i];
if(preDiff >= 0 && curDiff<0 || preDiff<=0 && curDiff>0){
result++;//注意这里的等号
preDiff = curDiff;
}
}
return result;
}
}
53 最大子数组和
【题目】
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
【局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小。】
【全局最优:选取最大“连续和”】
【示例】
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
【解答】
class Solution {
public int maxSubArray(int[] nums) {
int reslut = 0;
int count = nums[0];
for(int i = 1;i<nums.length;i++){
if(count + nums[i]>=0){
count = count + nums[i];
reslut = Math.max(reslut,count);
}else{
count = 0;
}
}
return reslut;
}
}
122 买卖股票的最佳时机II
【题目】
给定一个数组 prices ,其中 prices[i] 是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。【最小买卖单元是两个 】
【局部:收集每天的正利润】【全局最优:求得最大利润。】
【示例】
假如第0天买入,第3天卖出,那么利润为:prices[3] - prices[0]。
相当于(prices[3] - prices[2]) + (prices[2] - prices[1]) + (prices[1] - prices[0])。
此时就是把利润分解为每天为单位的维度,而不是从0天到第3天整体去考虑!
【注】计算所有的正利润就可以了
输入: prices = [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
输入: prices = [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
【解答】
class Solution {
public int maxProfit(int[] prices) {
if(prices.length <= 1){
return 0;
}
int reslut = 0;
for(int i = 1;i<prices.length;i++){
if(prices[i]-prices[i-1] > 0){
reslut += (prices[i]-prices[i-1]);
}
}
return reslut;
}
}
55 跳跃游戏
【题目】
给定一个非负整数数组 nums
,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。
【局部:能达到的最远位置】【整体:能否覆盖最后一个位置】
【示例】
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。
【解答】
class Solution {
public boolean canJump(int[] nums) {
//找可以覆盖的索引
int couCover = 0;
for(int i = 0;i<nums.length;i++){
if(couCover>=i){//说明能到当前位置
couCover = Math.max(couCover,i+nums[i]);//更新couCover
if(couCover>=nums.length-1){
return true;//剪枝 提前返回
}
}else{
return false;
}
}
return true;
}
}
45 跳跃游戏II
【题目】
给你一个非负整数数组 nums ,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。你的目标是使用最少的跳跃次数到达数组的最后一个位置。
假设你总是可以到达数组的最后一个位置。
【示例】
输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
输入: nums = [2,3,0,1,4]
输出: 2
【解答】
class Solution {
public int jump(int[] nums) {
int sum = 0;
int curReach = 0;//当前可到达的最远距离
int nextReach = 0;//下一步可达的最远距离
for(int i = 0;i<nums.length-1;i++){//倒数第一个位置肯定是可达的,肯定是可以被覆盖的,注意-1
nextReach = Math.max(nextReach,i+nums[i]);//最好提前更新一下
if(i == curReach){//对应curReach是0,sum = 0
sum++;
curReach = nextReach;
}
}
return sum;
}
}
int sum = 0;
int curReach = 0;//当前可到达的最远距离
int nextReach = 0;//下一步可达的最远距离
for(int i = 0;i<nums.length-1;i++){//倒数第一个位置肯定是可达的,肯定是可以被覆盖的,注意-1
nextReach = Math.max(nextReach,i+nums[i]);//最好提前更新一下
if(i == curReach){//对应curReach是0,sum = 0
sum++;
curReach = nextReach;
}
}
return sum>K?-1:sum;
1005 K次取反后最大化的数组和
注意将绝对值排序
【题目】
给你一个整数数组 nums 和一个整数 k ,按以下方法修改该数组:
选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。重复这个过程恰好 k 次。可以多次选择同一个下标 i 。
以这种方式修改数组后,返回数组 可能的最大和 。
【示例】
输入:nums = [4,2,3], k = 1
输出:5
解释:选择下标 1 ,nums 变为 [4,-2,3] 。
输入:nums = [3,-1,0,2], k = 3
输出:6
解释:选择下标 (1, 2, 2) ,nums 变为 [3,1,0,2] 。
输入:nums = [2,-3,-1,5,-4], k = 2
输出:13
解释:选择下标 (1, 4) ,nums 变为 [2,3,-1,5,4] 。
【解答】
class Solution {
public int largestSumAfterKNegations(int[] nums, int k) {
int sum = 0;
//将nums中的数值按照从大到小的顺序排序
nums = IntStream.of(nums)
.boxed()
.sorted((o1,o2)->Math.abs(o2)-Math.abs(o1))
.mapToInt(Integer::intValue).toArray();
int len = nums.length;
//将负数翻转
for(int i = 0;i<len;i++){
if(k>0 && nums[i]<0){
nums[i] = -nums[i];
k--;
}
if(k==0){
break;
}
}
//是否还有k剩余 是偶数还是奇数---奇数时将最后一个数值取反即可
if(k>0&&k%2!=0){
nums[len-1] = -nums[len-1];
}
for(int i :nums){
sum += i;
}
return sum;
}
}
134 加油站
【题目】
在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。
你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。
如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。
说明:
如果题目有解,该答案即为唯一答案。
输入数组均为非空数组,且长度相同。
输入数组中的元素均为非负数。
【示例】
输入:
gas = [1,2,3,4,5]
cost = [3,4,5,1,2]
输出: 3
解释:
从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
因此,3 可为起始索引。
输入:
gas = [2,3,4]
cost = [3,4,3]
输出: -1
解释:
你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。
我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油
开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油
开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油
你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。
因此,无论怎样,你都不可能绕环路行驶一周。
【解答】
全局最优,分情况讨论
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
/**
* 1、需要判断gas总量是不是>=cost总量,如果不满足,那不管从哪里出发,都不行
* 2、需要判断rest[i] = gas[i]-cost[i],若累加的最小值没有出现负数,则可从0出发
* 3、若累加的最小值是负数,那说明不能从0出发,不妨从后向前,看看哪一个结点能让最小值不为负数
*/
int curSum = 0;//累加和
int min = Integer.MAX_VALUE;//累加的最小值
for(int i = 0;i<gas.length;i++){
int rest = gas[i] - cost[i];//注意是谁-谁
curSum = curSum + rest;//当前位置攒的汽油
min = Math.min(min,curSum);
}
if(curSum < 0)return -1;//对应1、
if(min>=0) return 0;//对应2、可以从0出发
for(int i = gas.length-1;i>=0;i--){
int rest = gas[i] - cost[i];
min = min + rest;
if(min>=0){//填平
return i;
}
}
return -1;
}
}
贪心
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int start = 0;//开始累加,有可能作为开始位置
int total = 0;//所有的存量
int curSum = 0;//从start位置积攒的汽油量
for(int i = 0;i<gas.length;i++){
total = total + gas[i] - cost[i];
curSum = curSum + gas[i] -cost[i];
if(curSum<0){//当前start位置不能作为开始位置
curSum = 0;
start = i+1;//从下一位置重新算起
}
}
if(total < 0)return -1;//从哪个位置都不行
return start;
}
}
135 分发糖果
【题目】
老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。
你需要按照以下要求,帮助老师给这些孩子分发糖果:
每个孩子至少分配到 1 个糖果。
评分更高的孩子必须比他两侧的邻位孩子获得更多的糖果。
那么这样下来,老师至少需要准备多少颗糖果呢?
【示例】
输入:[1,0,2]
输出:5
解释:你可以分别给这三个孩子分发 2、1、2 颗糖果。
输入:[1,2,2]
输出:4
解释:你可以分别给这三个孩子分发 1、2、1 颗糖果。
第三个孩子只得到 1 颗糖果,这已满足上述两个条件。
【解答】
class Solution {
public int candy(int[] ratings) {
int sum = ratings.length;
int[] candynum = new int[ratings.length];
//从左向右更新 若ratings[i]>ratings[i-1] candynum[i] = candynum[i-1]+1
for(int i = 1;i<ratings.length;i++){
if(ratings[i-1]<ratings[i]){//保证左<右 糖果符合
candynum[i] = candynum[i-1] + 1;
}
}
//从右向左更新,若ratings[i]<ratings[i-1] candynum[i-1] = candynum[i] + 1;倒序
for(int i = ratings.length-2;i>=0;i--){
if(ratings[i]>ratings[i+1]&&candynum[i]<=candynum[i+1]){//注意这里需要单独判断,说明需要去改变,如果不加以判断的话,可能会存在 本来candynum[i]>candynum[i+1]+1,又更新 结果偏小
candynum[i] = candynum[i+1] + 1;
}
}
for(int i = 0;i<candynum.length;i++){
sum = sum + candynum[i];
}
return sum;
}
}
860 柠檬水找零
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CbH0GnXs-1649300405799)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20211219202900964.png)]
【示例】
输入:bills = [5,5,5,10,20]
输出:true
解释:
前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。
第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。
第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。
由于所有客户都得到了正确的找零,所以我们输出 true。
输入:bills = [5,5,10,10,20]
输出:false
解释:
前 2 位顾客那里,我们按顺序收取 2 张 5 美元的钞票。
对于接下来的 2 位顾客,我们收取一张 10 美元的钞票,然后返还 5 美元。
对于最后一位顾客,我们无法退回 15 美元,因为我们现在只有两张 10 美元的钞票。
由于不是每位顾客都得到了正确的找零,所以答案是 false
【解答】
class Solution {
public boolean lemonadeChange(int[] bills) {
int num5 = 0;
int num10 = 0;
for(int i = 0;i<bills.length;i++){
if(bills[i] == 5){
num5++;
}else if(bills[i] == 10){
num10++;
if(num5>0){
num5--;
}else{
return false;
}
}else{
if(num10>0&&num5>0){//找10+5
num10--;num5--;
}else if(num10<=0&&num5>=3){//找三个5元
num5 = num5 - 3;
}else{
return false;
}
}
}
return true;
}
}
406 根据身高重建队列
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UOeUOdzR-1649300405799)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20211219210630446.png)]
【示例】
输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
解释:
编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。
编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。
编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。
【解答】
class Solution {
public int[][] reconstructQueue(int[][] people) {
/**
* 先按身高从高到低,身高相同的,按ki从小到大 ---非常巧妙
*/
Arrays.sort(people,(a,b)->{
if(a[0] == b[0]) return a[1] - b[1];
return b[0] -a[0];
});
LinkedList<int[]> queue = new LinkedList<>();
for(int[] p:people){
queue.add(p[1],p);
}
return queue.toArray(new int[people.length][]);
}
}
435 无重叠子区间
按起始位置排序,若出现重叠,更新最右边为最小结束位置(即删掉结束位置靠右边的),同时result++
按结束位置排序,记录不重叠的区间个数
【题目】
给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
注意:
可以认为区间的终点总是大于它的起点。
区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。
【示例】
输入: [ [1,2], [2,3], [3,4], [1,3] ]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。
输入: [ [1,2], [1,2], [1,2] ]
输出: 2
解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。
输入: [ [1,2], [2,3] ]
输出: 0
解释: 你不需要移除任何区间,因为它们已经是无重叠的了。
【解答】
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
int result = 0;
Arrays.sort(intervals,(a,b)->Integer.compare(a[0],b[0]));
for(int i = 1;i<intervals.length;i++){
if(intervals[i][0]<intervals[i-1][1]){//出现重叠
//更新:模拟删除结束位置靠右的区间,故更新一下结点的最右位置
//更新后为靠左点的,为下一区间预留较大的位置
intervals[i][1] = Math.min(intervals[i][1],intervals[i-1][1]);
result++;//模拟删除
}
}
return result;
}
}
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
int count = 1;//记录非交叉位置的区间个数
//根据结束位置从小到大排序
Arrays.sort(intervals,(a,b)->Integer.compare(a[1],b[1]));
int end = intervals[0][1];//初始值是所有区间中结束位置最小的(为下一个区间预留最大的空间)
for(int i = 1;i<intervals.length;i++){
if(end <= intervals[i][0]){//出现不重叠的区间
count++;
end = intervals[i][1];//更新
}
}
return intervals.length - count;
}
}
763 划分字母区间
先计算所有字母出现的最远位置,若当前位置与当前字符串的最远位置相同时,表示可以切割
【题目】
字符串 S
由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。
【示例】
输入:S = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca", "defegde", "hijhklij"。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 的划分是错误的,因为划分的片段数较少。
【解答】
class Solution {
public List<Integer> partitionLabels(String s) {
//记录每个字母的最远出现下标,若到达当前最远位置时,表示可以分隔
List<Integer> resList = new ArrayList<>();
int[] index = new int[26];
for(int i = 0;i<s.length();i++){
index[s.charAt(i)-'a'] = i;//更新坐标 right
}
int farest = 0;
int lastnum = 0;//需要一个变量来记录上一个最远位置
for(int i = 0;i<s.length();i++){
farest = Math.max(farest,index[s.charAt(i)-'a']);
if(farest == i){
int num = farest - lastnum + 1;
lastnum = farest + 1;
resList.add(num);
}
}
return resList;
}
}
56 合并区间
【题目】
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回一个不 重叠的区间数组,该数组需恰好覆盖输入中的所有区间。
【示例】
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
【解答】
class Solution {
public int[][] merge(int[][] intervals) {
List<int[]> resList = new ArrayList<>();//int数组链 int[] -> int[] -> int[]...
//按区间起始位置排序
Arrays.sort(intervals,(a,b)->Integer.compare(a[0],b[0]));
int start = intervals[0][0];//构造返回结果区间的起始位置
int end = intervals[0][1];//构造返回结果区间的终止位置
for(int i = 1;i<intervals.length;i++){
if(intervals[i][0] <= end){//可以合并
end = Math.max(end,intervals[i][1]);
}else{
//构造返回结果
resList.add(new int[]{start,end});//构造一个数组 内容是{ , , ...}
start = intervals[i][0];
end = intervals[i][1];
}
}
resList.add(new int[]{start,end});
return resList.toArray(new int[resList.size()][]);//数组链转为数组
}
}
738 单调递增的数字
【题目】
给定一个非负整数 N,找出小于或等于 N 的最大的整数,同时这个整数需要满足其各个位数上的数字是单调递增。
(当且仅当每个相邻位数上的数字 x 和 y 满足 x <= y 时,我们称这个整数是单调递增的。)
找到开始全被设为9的索引
【解答】
输入: N = 100
输出: 99
输入: N = 1234
输出: 1234
输入: N = 332
输出: 299
class Solution {
public int monotoneIncreasingDigits(int n) {
char[] strnum = Integer.toString(n).toCharArray();
int start = Integer.MAX_VALUE;//设为最大值,其实为长度也行
for(int i = strnum.length - 1;i>0;i--){
if(strnum[i]<strnum[i-1]){
strnum[i-1]--;//--- 注意这里strnum[i-1] = strnum[i-1] - 1;是不对的
start = i;//更新要开始被设为9的下标
}
}
for(int i = start;i<strnum.length;i++){
strnum[i] = '9';
}
StringBuilder sb = new StringBuilder();
sb.append(strnum);
return Integer.parseInt(sb.toString());
}
}
714 买卖股票的最佳时机含手续费
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OTHWaty0-1649300405799)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20211226150647991.png)]
【示例】
输入:prices = [1, 3, 2, 8, 4, 9], fee = 2
输出:8
解释:能够达到的最大利润:
在此处买入 prices[0] = 1
在此处卖出 prices[3] = 8
在此处买入 prices[4] = 4
在此处卖出 prices[5] = 9
总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8
输入:prices = [1,3,7,5,10,3], fee = 3
输出:6
【解答】
情况一:收获利润的这一天并不是收获利润区间里的最后一天(不是真正的卖出,相当于持有股票),所以后面要继续收获利润。
情况二:前一天是收获利润区间里的最后一天(相当于真正的卖出了),今天要重新记录最小价格了。
情况三:不作操作,保持原有状态(买入,卖出,不买不卖)
class Solution {
public int maxProfit(int[] prices, int fee) {
int result = 0;//记录利润
int minPrice = prices[0];
for(int i = 1;i<prices.length;i++){
if(prices[i] < minPrice){
minPrice = prices[i];//情况一:更新最小利润,相当于把之前的股票投出,重新买入
}
if(prices[i]>=minPrice && prices[i]<minPrice+fee){
continue;//情况三:相当于买入不便宜,卖了就得贴钱
}
if(prices[i] > minPrice + fee){
result = result + prices[i] - (minPrice + fee);
minPrice = prices[i] - fee;//更新最小利润,其实就是看看之后会不会比现在获得的利润多
}
}
return result;
}
}
968 监控二叉树
【题目】
给定一个二叉树,我们在树的节点上安装摄像头。节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。
计算监控树的所有节点所需的最小摄像头数量。
【示例】
输入:[0,0,null,0,0]
输出:1
解释:如图所示,一台摄像头足以监控所有节点。
【解答】
输入:[0,0,null,0,null,0,null,null,0]
输出: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 result = 0;
public int minCameraCover(TreeNode root) {
//需要单独判断root结点
if(traversal(root)==0){
result++;
}
return result;
}
/**
0:无覆盖
1:有摄像头
2:有覆盖
*/
public int traversal(TreeNode node){
if(node == null) return 2;
//后序遍历
int left = traversal(node.left);
int right = traversal(node.right);
//有任意一个孩子没有被覆盖,当前 结点都应该为1:安上一个摄像头
if(left == 0 || right == 0){
result++;
return 1;
}
//有任一个孩子有摄像头,当前节点都会被覆盖
if(left == 1 || right==1){
return 2;
}
//两个孩子都被覆盖了,那当前结点没有被覆盖 left == 2 && right == 2
return 0;
}
}
·动态规划
五部曲
-
确定dp数组(dp table)以及下标的含义:一般与问题相关
-
确定递推公式: 一般与前两个相关
-
dp数组如何初始化:注意是不是从下标0初始化 其他下标是要初始化成最大最小值还是0,若是最大最小时,注意求和时判断
-
确定遍历顺序:从前向后,还是从后向前,遍历的第一个下标
组合:先物品再背包 排序:先背包再物品
01背包:从后向前 完全背包:从前向后
-
举例推导dp数组:最好根据实例模拟一下,注意题目给的下标区间范围
509 斐波那契数
【题目】
斐波那契数,通常用 F(n)
表示,形成的序列称为 斐波那契数列 。该数列由 0
和 1
开始,后面的每一项数字都是前面两项数字的和。也就是:给你 n
,请计算 F(n)
。
【示例】
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
输入:2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1
输入:4
输出:3
解释:F(4) = F(3) + F(2) = 2 + 1 = 3
提示:
0 <= n <= 30
【解答】
迭代
class Solution {
public int fib(int n) {
if(n == 0){
return 0;
}else if(n == 1){
return 1;
}else{
return fib(n-1) + fib(n-2);
}
}
}
DP
class Solution {
public int fib(int n) {
if(n <2){
return n;
}
int first = 0,second = 1;
for(int i = 2;i<=n;i++){
int temp = first + second;
first = second;
second = temp;
}
return second;
}
}
70 爬楼梯
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VqjxCN66-1649300405800)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20211230103510720.png)]
【示例】
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
【解答】
class Solution {
/**
f(n)=f(n-1)+f(n-2);
*/
public int climbStairs(int n) {
if(n<3){
return n;
}
int first = 1,second = 2;
for(int i = 3;i<=n;i++){
int temp = first + second;
first = second;
second = temp;
}
return second;
}
}
746 花费最小爬楼梯
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WUbjZFia-1649300405800)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20211230113434708.png)]
【示例】
-
输入:cost = [10,15,20] 输出:15 解释:你将从下标为 1 的台阶开始。 - 支付 15 ,向上爬两个台阶,到达楼梯顶部。 总花费为 15 。
【解答】
class Solution {
public int minCostClimbingStairs(int[] cost) {
/**
f[i] = min(f[i-1],f[i-2])+cost[i];
*/
int first = cost[0];
int second = cost[1];//你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。
for(int i = 2;i<cost.length;i++){
//第i个阶梯可以由上一个或上上一个到达,且这个楼梯向上需要花费cost[i]
int temp = Math.min(first,second) + cost[i];
first = second;
second = temp;
}
return Math.min(first,second);//这是关键
}
}
62 不同路径
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xd2hdunT-1649300405801)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20211230153218938.png)]
【示例】
输入:m = 3, n = 7
输出:28
输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右
3. 向下 -> 向右 -> 向下
【解答】
class Solution {
public int uniquePaths(int m, int n) {
/**
dp[i][j] = dp[i-1][j] + dp[i][j-1]
初始化:dp[i][0] = 1 dp[0][j] = 1
*/
int[][] dp = new int[m][n];
for(int i = 0;i<m;i++){
dp[i][0] = 1;
}
for(int j = 1;j<n;j++){
dp[0][j] = 1;
}
for(int i = 1;i<m;i++){
for(int j = 1;j<n;j++){
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
return dp[m-1][n-1];
}
}
63 不同路径
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l8o4u8iC-1649300405801)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20211230155058644.png)]
【示例】
输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右
【解答】
class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
/**
dp[i][j] = dp[i-1][j] + dp[i][j-1],若没有障碍时
初始化时,注意若有障碍,后续的就不用初始化为1了
*/
int m = obstacleGrid.length,n = obstacleGrid[0].length;
int[][] dp = new int[m][n];
for(int i = 0;i<m&&obstacleGrid[i][0]==0;i++){
dp[i][0] = 1;
}
for(int j = 0;j<n&&obstacleGrid[0][j]==0;j++){
dp[0][j] = 1;
}
for(int i = 1;i<m;i++){
for(int j = 1;j<n;j++){
if(obstacleGrid[i][j] == 0){
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
}
return dp[m-1][n-1];
}
}
343 整数拆分
【题目】
给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。
【示例】
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。
【解答】
class Solution {
public int integerBreak(int n) {
/**
dp[i]:正整数i分成至少两个正整数的和,获得的最大乘积
dp[i] = max{dp[i],max{j*(i-j),j*dp[i-j]}}
需要注意dp[i]也需要在max{}中,因为需要和上一个j生成的dp[i]对比
需要注意i j的取值范围
题目中已经给出i>=2 故有dp[2] dp[3] ...dp[n]
j从1开始没有疑问 j的最大取值为i-1还是i-2 选择i-2是因为i-j最小是2 i-(i-2) = 2
*/
int[] dp = new int[n+1];
dp[2] = 1;//2 = 1+1
for(int i = 3;i <= n;i++){
for(int j = 1;j<=i-2;j++){
dp[i] = Math.max(dp[i],Math.max(j*(i-j),j*dp[i-j]));
}
}
return dp[n];
}
}
96 不同的二叉树
【题目】
给你一个整数 n
,求恰由 n
个节点组成且节点值从 1
到 n
互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
【示例】
输入:n = 3
输出:5
【解答】
class Solution {
public int numTrees(int n) {
/**
n= 0 = 1的二叉树有1个,n=2有2个(可以理解成:以1为头节点,右1,左0;以2为头节点,右0,左1)
n = 3的:1为头,右2左0;2为头右1左1;3为头,右0左2(0,1,2表示结点个数)
dp[i]表示n = i时,形成的二叉树的个数
dp[i] = dp[i] + dp[j]*dp[i-j-1];j∈[0,i-1],即从左0:右i-1,累加到左i-1:右0
dp[i]表示结点个数为i的二叉树的种类数
*/
int[] dp = new int[n+1];
dp[0] = 1;
dp[1] = 1;
for(int i = 2;i<=n;i++){
for(int j = 0;j<=i-1;j++){
dp[i] = dp[i] + dp[j]*dp[i-j-1];
}
}
return dp[n];
}
}
416 分割等和子集
【题目】
给你一个 只包含正整数 的 非空 数组 nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
【示例】
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
【解答】
一个物品只能放入一次
class Solution {
public boolean canPartition(int[] nums) {
/**
dp[j]:容量为j的背包,可以装的物品价值总量(容量为sum/2的背包,能不能装下的物品正好是sum/2)
注意变量的顺序,只能是从后向前遍历,防止同一个元素被放入多次
初始化时注意元素是不是都是非负整数,若不是,则需要初始化为最小值
*/
int sum = 0;
for(int i = 0;i<nums.length;i++){
sum = sum + nums[i];
}
if(sum%2 == 1)return false;//这个可以提前返回
int bagweight = sum/2;
int[] dp = new int[bagweight+1];//注意初始化的容量
for(int i = 0;i<nums.length;i++){//物品
for(int j = bagweight;j>=nums[i];j--){//背包
dp[j] = Math.max(dp[j],dp[j-nums[i]]+nums[i]);
}
}
if(dp[bagweight] == bagweight){
return true;
}else{
return false;
}
}
}
1049 最后一块石头的重量
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AoI1r3Ud-1649300405802)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20211231154410899.png)]
【示例】
输入:stones = [2,7,4,1,8,1]
输出:1
解释:
组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1],
组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1],
组合 2 和 1,得到 1,所以数组转化为 [1,1,1],
组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。
输入:stones = [31,26,33,21,40]
输出:5
【解答】
class Solution {
public int lastStoneWeightII(int[] stones) {
/**
尽量将所有的石头分为相等的两份
dp[j]:容量为j的背包最多可以装dp[j]的石头。j = sum / 2
dp[j] = max{dp[j],dp[j-stones[i]]+stones[i]}
*/
int sum = 0;
for(int i = 0;i<stones.length;i++){
sum += stones[i];
}
int bagweight = sum/2;
int[] dp = new int[bagweight+1];//注意数组初始化时+1
for(int i = 0;i<stones.length;i++){
for(int j = bagweight;j>=stones[i];j--){
dp[j] = Math.max(dp[j],dp[j-stones[i]]+stones[i]);
}
}
return (sum - dp[bagweight]) - dp[bagweight];
}
}
494 目标和
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PaaS0OgB-1649300405802)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20211231164034814.png)]
【示例】
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3
【解答】
class Solution {
public int findTargetSumWays(int[] nums, int target) {
/**
将数组分为两部分left right其中left前面全部加“+”,right前面全部加“-”
因为left - right = target (a)
left + right = sum (b) ((a)+(b))/2 = left
即从nums中找到和为left的数字即可
dp[j]:和为j的方法为dp[j]
**********求组合常用推导公式*************
dp[j] = dp[j] + dp[j-num[i]];//对于某个数值nums[i],有它的组合数=没有它(其实把它考虑了)的所有组合数之和
初始化:dp[0] = 1和为0的方法有1种
*/
int sum = 0;
for(int i = 0;i<nums.length;i++){
sum += nums[i];
}
if(target>sum)return 0;
if((sum+target)%2==1)return 0;
int bagweight = (sum + target)/2;
if(bagweight<0) return 0;//因为需要保证left是正数相加的和
int[] dp = new int[bagweight+1];
dp[0] = 1;//初始化
for(int i = 0;i<nums.length;i++){
for(int j = bagweight;j>=nums[i];j--){
dp[j] = dp[j] + dp[j-nums[i]];
}
}
return dp[bagweight];
}
}
474 一和零
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o3b4yIOM-1649300405803)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220102110551461.png)]
【示例】
输入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3
输出:4
解释:最多有 5 个 0 和 3 个 1 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。
其他满足题意但较小的子集包括 {"0001","1"} 和 {"10","1","0"} 。{"111001"} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3
输入:strs = ["10", "0", "1"], m = 1, n = 1
输出:2
解释:最大的子集是 {"0", "1"} ,所以答案是 2 。
【解答】
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
/**
物品的重量是二维的,dp[i][j]含有i个零,j个一的子集大小
dp[i][j] = max{dp[i][j],dp[i-zeroNum][j-oneNum]+1}
value相当于1,就是个数,重量二维的
初始化均为0,因为每一个字符串中0 1个数都是非负整数
*/
int[][] dp = new int[m+1][n+1];
for(int k = 0;k<strs.length;k++){//对应物品
int zeroNum = 0,oneNum = 0;
for(int index = 0;index<strs[k].length();index++){//计算这个字符串的0 1个数
char s = strs[k].charAt(index);
if(s == '0'){
zeroNum++;
}else{
oneNum++;
}
}
for(int i = m;i>=zeroNum;i--){//对应weight
for(int j = n;j>=oneNum;j--){//注意遍历顺序
dp[i][j] = Math.max(dp[i][j],dp[i-zeroNum][j-oneNum]+1);
}
}
}
return dp[m][n];
}
}
518 零钱兑换
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fCghHRoN-1649300405803)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220102114824877.png)]
【示例】
输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
【解答】
class Solution {
public int change(int amount, int[] coins) {
/**
dp[j]:总金额j的组合数为dp[j]
dp[j] = dp[j] + dp[j-coins[i]]:组合数中有coins[i]的是所有没有coins[i]的组合数总和
遍历顺序:先物品,再重量(金额)是组合
先重量,再物品是排序
初始化;dp[0] = 1
*/
int[] dp = new int[amount+1];
dp[0] =1;
for(int i = 0;i<coins.length;i++){//物品
for(int j = coins[i];j<=amount;j++){//可以多次利用--背包
dp[j] = dp[j] + dp[j-coins[i]];
}
}
return dp[amount];
}
}
377 组合数IV
【题目】
给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。题目数据保证答案符合 32 位整数范围。其实是排序数
【示例】
输入:nums = [1,2,3], target = 4
输出:7
解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。
【解答】
class Solution {
public int combinationSum4(int[] nums, int target) {
/**
dp[j]:和为j的排列数,虽然题目中说的是组合数,但是例子中是有顺序的
dp[j] = dp[j] + dp[j-nums[i]]
dp[0] = 1
遍历顺序:先遍历重量(j),再遍历物品(数字),都是从前向后遍历
*/
int[] dp = new int[target+1];
dp[0] = 1;
for(int j = 0;j<=target;j++){//背包
for(int i = 0;i<nums.length;i++){//物品
if(j>=nums[i]){//注意这个条件
dp[j] = dp[j] + dp[j-nums[i]];
}
}
}
return dp[target];
}
}
70 爬楼梯
【题目】
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
【示例】
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
【解答】
class Solution {
public int climbStairs(int n) {
/**
完全背包问题:
dp[j]:和为j的排列数
dp[j] = dp[j] + dp[j-i];//i表示物品,重量为1或2.即可以爬的台阶数
顺序::先遍历背包重量,再遍历物品,从前向后,表示可以多次选取
*/
int[] dp = new int[n+1];
dp[0] = 1;
for(int j = 0;j<=n;j++){//背包
for(int i = 1;i<=2;i++){//i = 1 = 2 物品 有两种选择--1 --2
if(j-i>=0){
dp[j] = dp[j] + dp[j-i];
}
}
}
return dp[n];
}
}
322 零钱兑换II
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y6hpmlUy-1649300405803)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220102180828384.png)]
【示例】
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
输入:coins = [2], amount = 3
输出:-1
输入:coins = [1], amount = 0
输出:0
【解答】
class Solution {
public int coinChange(int[] coins, int amount) {
/**
完全背包问题
dp[j]:总金额为j所需硬币的个数最少为dp[j]
dp[j] = min{dp[j],dp[j-coins[i]]+1}
dp[0] = 0,其他都需要初始化为MAX_VALUE
遍历顺序:物品也可,重量也可,没有要求,只跟个数有关 从前向后,因为数量无限
*/
int[] dp = new int[amount+1];
dp[0] = 0;
for(int i = 1;i<dp.length;i++){
dp[i] = Integer.MAX_VALUE;
}
for(int i = 0;i<coins.length;i++){
for(int j = coins[i];j<=amount;j++){
if(dp[j-coins[i]] != Integer.MAX_VALUE){//如果是初始值,则跳过,防止溢出---注意!!!
dp[j] = Math.min(dp[j],dp[j-coins[i]]+1);
}
}
}
if(dp[amount] == Integer.MAX_VALUE){
return -1;
}else{
return dp[amount];
}
}
}
279 完全平方数
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cS5OnAoZ-1649300405804)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220103120427258.png)]
【示例】
输入:n = 12
输出:3
解释:12 = 4 + 4 + 4
输入:n = 13
输出:2
解释:13 = 4 + 9
【解答】
class Solution {
public int numSquares(int n) {
/**
dp[j]:和为j的完全平方数的最少数量
dp[j] = min(dp[j],dp[j-i*i]+1) j相当于背包容量 i*i相当于物品重量 1是value
初始化:dp[0] = 0 其他都是最大值 因为n最小为1
遍历顺序从前向后 先遍历物品还是背包无关
*/
int[] dp = new int[n+1];
dp[0] = 0;
for(int i = 1;i<=n;i++){
dp[i] = Integer.MAX_VALUE;
}
for(int i = 1;i*i<=n;i++){
for(int j = i*i;j<=n;j++){
if(dp[j-i*i] != Integer.MAX_VALUE){
dp[j] = Math.min(dp[j],dp[j-i*i]+1);//
}
}
}
return dp[n];
}
}
139 单词拆分
【题目】
给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。
注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
【示例】
输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以由 "apple" "pen" "apple" 拼接成。
注意,你可以重复使用字典中的单词。
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false
【解答】
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
/**
dp[i]:长度为i,且dp[i]为true
dp[i] = (dp[j] && str[j+1,...,i]在字典中)
dp[0]为true,其他为false
先背包再物品还是先物品再背包都可以
可以重复使用:完全背包,从前向后
*/
boolean[] dp = new boolean[s.length()+1];
dp[0] = true;
for(int i = 1;i<=s.length();i++){//背包,对应s的子串长度
for(int j = 0;j<i;j++){//截取的子串 当成物品
if(dp[j]&&wordDict.contains(s.substring(j,i))){//注意下标
dp[i] = true;
}
}
}
return dp[s.length()];
}
}
198 打家劫舍
【题目】
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
房屋是线性排列的
【示例】
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
【解答】
class Solution {
public int rob(int[] nums) {
/**
dp[i]:下标i以内的房屋,盗窃最多为dp[i]
dp[i] = max{dp[i-1],dp[i-1]+nums[i]}//区别在于nums[i]被不被偷盗
dp[0] = nums[0] dp[1] = max{nums[0],nums[1]}
从前向后遍历
*/
if(nums.length == 1){
return nums[0];
}
int first = nums[0],second = Math.max(nums[0],nums[1]);
for(int i = 2;i<nums.length;i++){
int temp = Math.max(first+nums[i],second);
first = second;
second = temp;
}
return second;
}
}
213 打家劫舍II
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aD3PLi0W-1649300405804)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220104173237180.png)]
【示例】
输入:nums = [2,3,2]
输出:3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
输入:nums = [1,2,3,1]
输出:4
解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4
【解答】
class Solution {
public int rob(int[] nums) {
/**
考虑三种情况:不考虑最后一个
不考虑第一个
第一个和最后一个都不考虑
注意:考虑不一定要加进去
*/
if(nums.length == 1){
return nums[0];
}
int result1 = robSub(0,nums.length-1,nums);//不考虑最后一个
int result2 = robSub(1,nums.length,nums);//不考虑第一个
return Math.max(result1,result2);
}
private int robSub(int start,int end,int[] nums){//[start,end)
if(end - start == 1){
return nums[start];
}
int first = nums[start],second = Math.max(nums[start],nums[start+1]);
for(int i = start+2;i<end;i++){
int temp = Math.max(first+nums[i],second);
first = second;
second = temp;
}
return second;
}
}
337 打家劫舍III
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L9jgRsho-1649300405805)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220104180246274.png)]
【示例】
输入: [3,2,3,null,3,null,1]
3
/ \
2 3
\ \
3 1
输出: 7
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.
输入: [3,4,5,1,3,null,1]
3
/ \
4 5
/ \ \
1 3 1
输出: 9
解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.
【解答】
/**
* 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) {
/**
后序遍历
递归和动态规划的结合
[0]:不偷该结点所得的金额的最大值
[1]:偷该结点所得的金额的最大值 return max{[0],[1]}
left = traversal(cur->left)
right = traversal(cur->right)
不偷该节点:max{left[0],left[1]}+max{right[0],right[1]} 若不偷该节点 左右孩子可偷可不偷
偷该结点:cur->val + left[0] + right[0],若偷该节点,左右孩子必须不能偷
初始化与结束递归条件相同
*/
int[] result = traversal(root);
return Math.max(result[0],result[1]);
}
private int[] traversal(TreeNode cur){
if(cur == null) return new int[]{0,0};
int[] left = traversal(cur.left);
int[] right = traversal(cur.right);
//偷
int val1 = cur.val + left[0] + right[0];
//不偷 孩子节点 可偷可不偷
int val2 = Math.max(left[0],left[1])+Math.max(right[0],right[1]);
return new int[]{val2,val1};
}
}
121 买卖股票的最佳时机
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lfsEbPaz-1649300405805)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220104203712056.png)]
【示例】
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
【解答】
class Solution {
public int maxProfit(int[] prices) {
/**
dp[i][0]:持有股票时获得的最大金额
dp[i][1]:不持有股票时获得的最大金额
dp[i][0] = max{dp[i-1],-prices[i]}//前一天就持有 ,今天才买入
dp[i][1] = max{dp[i-1][1],prices[i]+dp[i-1][0]}//前一天就不持有,今天才卖出 注意这里是加号
初始化:dp[0][0] = -prices[0] dp[0][1] = 0
也可以 dp[i][0] = Math.min(dp[i-1][0],prices[i]); dp[0][0] = prices[0];//注意是正值
dp[i][1] = Math.max(dp[i-1][1],prices[i]-dp[i-1][0]);
*/
int[][] dp = new int[prices.length][2];
dp[0][0] = prices[0];//注意是负值
dp[0][1] = 0;
for(int i = 1;i<prices.length;i++){
dp[i][0] = Math.min(dp[i-1][0],prices[i]);
dp[i][1] = Math.max(dp[i-1][1],prices[i]-dp[i-1][0]);
}
return dp[prices.length-1][1];
}
}
class Solution {
public int maxProfit(int[] prices) {
/**
dp[i][0]:持有股票时获得的最大金额
dp[i][1]:不持有股票时获得的最大金额
dp[i][0] = max{dp[i-1],-prices[i]}//前一天就持有 ,今天才买入
dp[i][1] = max{dp[i-1][1],prices[i]+dp[i-1][0]}//前一天就不持有,今天才卖出 注意这里是加号
初始化:dp[0][0] = -prices[0] dp[0][1] = 0
*/
int[][] dp = new int[prices.length][2];
dp[0][0] = prices[0];//注意是负值
dp[0][1] = 0;
for(int i = 1;i<prices.length;i++){
dp[i][0] = Math.min(dp[i-1][0],prices[i]);
dp[i][1] = Math.max(dp[i-1][1],prices[i]-dp[i-1][0]);
}
return dp[prices.length-1][1];
}
}
122 买卖股票的最佳时机II
【题目】
可以多次买卖
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uyIncnry-1649300405805)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220104205433083.png)]
【示例】
输入: prices = [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
【解答】
可以多次买卖
class Solution {
public int maxProfit(int[] prices) {
//将正值相加
if(prices.length <= 1){
return 0;
}
int reslut = 0;
for(int i = 1;i<prices.length;i++){
if(prices[i]-prices[i-1] > 0){
reslut += (prices[i]-prices[i-1]);
}
}
return reslut;
}
}
class Solution {
public int maxProfit(int[] prices) {
/**
dp[i][0]:持有股票时获得的最大金额
dp[i][1]:不持有股票时获得的最大金额
dp[i][0] = max{dp[i-1],dp[i-1][1]-prices[i]}//前一天就持有 ,今天才买入
dp[i][1] = max{dp[i-1][1],prices[i]+dp[i-1][0]}//前一天就不持有,今天才卖出 注意这里是加号
初始化:dp[0][0] = -prices[0] dp[0][1] = 0
*/
int[][] dp = new int[prices.length][2];
dp[0][0] = -prices[0];
dp[0][1] = 0;
for(int i = 1;i<prices.length;i++){
dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]-prices[i]);
dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]+prices[i]);
}
return dp[prices.length-1][1];
}
}
123 买卖股票的最佳时机III
【题目】
最多两次买卖
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
【示例】
输入:prices = [3,3,5,0,0,3,1,4]
输出:6
解释:在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3
【解答】
最多两次
class Solution {
public int maxProfit(int[] prices) {
/**
j = 0,无操作
j = 1,第1次持有股票 j = 2,第1次不持有股票
j = 3,第2次持有股票 j = 4,第2次不持有股票
dp[i][j]:第i天的状态为j
dp[i][1] = max{dp[i-1][1],dp[i-1][0]-prices[i]} 之前就持有 今天才买入
dp[i][2] = max{dp[i-1][2],prices[i]+dp[i-1][1]}
dp[i][3] = max{dp[i-1][3],dp[i-1][2]-prices[i]}
dp[i][4] = max{dp[i-1][4],prices[i]+dp[i-1][3]}
dp[0][0] = dp[0][2] = dp[0][4] = 0
dp[0][1] = dp[0][3] = -prices[0]
*/
int[][] dp = new int[prices.length][5];
dp[0][1] = dp[0][3] = -prices[0];
for(int i = 1;i<prices.length;i++){
dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]-prices[i]);
dp[i][2] = Math.max(dp[i-1][2],dp[i-1][1]+prices[i]);
dp[i][3] = Math.max(dp[i-1][3],dp[i-1][2]-prices[i]);
dp[i][4] = Math.max(dp[i-1][4],dp[i-1][3]+prices[i]);
}
return dp[prices.length-1][4];
}
}
188 买卖股票的最佳时机IV
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DMzomhmE-1649300405806)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220105163958202.png)]
【示例】
输入:k = 2, prices = [3,2,6,5,0,3]
输出:7
解释:在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4 。
随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。
【解答】
最多k次
class Solution {
public int maxProfit(int k, int[] prices) {
/**
j = 0,无操作
m = 2j-1,第j次持有股票 m= 2j,第j次不持有股票 j = 0,1,...
dp[i][m]:第i天的状态为m
dp[i][2j-1] = max{dp[i-1][2j-1],dp[i-1][2j-2]-prices[i]} 之前就持有 今天才买入
dp[i][2j] = max{dp[i-1][2j],dp[i-1][2j-1]+prices[i]}
dp[0][2j] = 0
dp[0][2j-1] = -prices[0] 1(1),3(2),5(3)..,2j-1(k) j = 1,2,3..k
*/
if(prices.length == 0){
return 0;
}
int[][] dp = new int[prices.length][2*k+1];//0.1.2....2k
//初始化
for(int j = 1;j<=k;j++){
dp[0][2*j-1] = -prices[0];
}
for(int i = 1;i<prices.length;i++){
for(int j = 1;j<=k;j++){
dp[i][2*j-1] = Math.max(dp[i-1][2*j-1],dp[i-1][2*j-2]-prices[i]);//奇数持有 可能买入
dp[i][2*j] = Math.max(dp[i-1][2*j],dp[i-1][2*j-1]+prices[i]);//偶数 不持有--可能卖出
}
}
return dp[prices.length-1][2*k];
}
}
309 买卖股票的最佳时机含冷冻期
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tXJSxbCl-1649300405806)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220106114311235.png)]
【示例】
输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
【解答】
将不持有分为几种状态
class Solution {
public int maxProfit(int[] prices) {
/**
dp[i][j]:第i天是状态j
j = 0:今天是持有股票状态(之前买入或今天买入)--今天买入(昨天是保持卖出状态,昨天是冷冻期)
j = 1:今天保持卖出状态(昨天也是卖出状态 昨天是冷冻期)
j = 2:今天卖出
j = 3:今天是冷冻期(昨天卖出)
dp[i][0] = max{dp[i-1][0],dp[i-1][1]-prices[i],dp[i-1][3]-prices[i]}
dp[i][1] = max{dp[i-1][1],dp[i-1][3]}
dp[i][2] = dp[i-1][0] + prices[i]
dp[i][3] = dp[i-1][2]
*/
int[][] dp = new int[prices.length][4];
dp[0][0] = -prices[0];//其他初始化均为0
for(int i = 1;i<prices.length;i++){
dp[i][0] = Math.max(dp[i-1][0],Math.max(dp[i-1][1],dp[i-1][3])-prices[i]);
dp[i][1] = Math.max(dp[i-1][1],dp[i-1][3]);
dp[i][2] = dp[i-1][0] + prices[i];
dp[i][3] = dp[i-1][2];
}
return Math.max(dp[prices.length-1][1],Math.max(dp[prices.length-1][2],dp[prices.length-1][3]));//注意返回值
}
}
714 买卖股票的最佳时机含手续费
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uFOG0pxj-1649300405806)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220106155635570.png)]
【示例】
输入:prices = [1, 3, 2, 8, 4, 9], fee = 2
输出:8
解释:能够达到的最大利润:
在此处买入 prices[0] = 1
在此处卖出 prices[3] = 8
在此处买入 prices[4] = 4
在此处卖出 prices[5] = 9
总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8
【解答】
贪心算法
class Solution {
public int maxProfit(int[] prices, int fee) {
int result = 0;//记录利润
int minPrice = prices[0];
for(int i = 1;i<prices.length;i++){
if(prices[i] < minPrice){
minPrice = prices[i];//情况一:更新最小利润,相当于把之前的股票投出,重新买入
}
if(prices[i]>=minPrice && prices[i]<minPrice+fee){
continue;//情况三:相当于买入不便宜,卖了就得贴钱
}
if(prices[i] > minPrice + fee){
result = result + prices[i] - (minPrice + fee);
minPrice = prices[i] - fee;//更新最小利润,其实就是看看之后会不会比现在获得的利润多
}
}
return result;
}
}
动态规划
class Solution {
public int maxProfit(int[] prices, int fee) {
/**
dp[i][j]:第i天的状态是j
j = 0:持有股票 dp[i][0] = max{dp[i-1][0],dp[i-1][1]-prices[i]}
j = 1:不持有股票 dp[i][1] = max{dp[i-1][1],dp[i-1][0]+prices[i]-fee}
*/
int[][] dp = new int[prices.length][2];
dp[0][0] = -prices[0];
for(int i = 1;i<prices.length;i++){
dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]-prices[i]);
dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]+prices[i]-fee);
}
return dp[prices.length-1][1];
}
}
300 最长递增子序列
【题目】
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
【示例】
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
【解答】
class Solution {
public int lengthOfLIS(int[] nums) {
/**
dp[i]:包含下标i在内的最长严格递增子序列的长度
dp[i] = max{dp[i],dp[j]+1} j = 0,1,...i-1 注意需要加1
初始化 每个下标初始化为1,即仅含有自身组成一个子序列
*/
int[] dp = new int[nums.length];
for(int i = 0;i<nums.length;i++){
dp[i] = 1;
}
for(int i = 1;i<nums.length;i++){
for(int j = 0;j<i;j++){
if(nums[i]>nums[j]){//若nums[j]<nums[i],则考虑将nums[j]加入递增子序列中
dp[i] = Math.max(dp[i],dp[j]+1);
}
}
//System.out.print(" "+dp[i]);
}
//return dp[nums.length-1];//不能保证最长的子序列中是包含最后一个下标的
int result = 0;
for(int i = 0;i<nums.length;i++){
result = Math.max(result,dp[i]);
}
return result;
}
}
674 最长连续递增子序列
【题目】
给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。
连续递增的子序列 可以由两个下标 l 和 r(l < r)确定,如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], …, nums[r - 1], nums[r]] 就是连续递增子序列。
【示例】
输入:nums = [1,3,5,4,7]
输出:3
解释:最长连续递增序列是 [1,3,5], 长度为3。
尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为 5 和 7 在原数组里被 4 隔开。
【解答】
class Solution {
public int findLengthOfLCIS(int[] nums) {
/**
dp[i]:以i为子序列最终位置的子序列长度
*/
int[] dp = new int[nums.length];
Arrays.fill(dp,1);
for(int i = 0;i<nums.length-1;i++){
if(nums[i+1]>nums[i]){//体现连续
dp[i+1] = dp[i] + 1;
}
}
int result = 0;
for(int i = 0;i<nums.length;i++){
result = Math.max(result,dp[i]);
}
return result;
}
}
718 最长重复子数组–连续
【题目】
给两个整数数组 A
和 B
,返回两个数组中公共的、长度最长的子数组的长度。
【示例】
输入:
A: [1,2,3,2,1]
B: [3,2,1,4,7]
输出:3
解释:
长度最长的公共子数组是 [3, 2, 1] 。
【解答】
class Solution {
public int findLength(int[] nums1, int[] nums2) {
/**
dp[i][j]:A中下标以i-1结尾 B中下标以j-1结尾的最长重复子数组的长度
dp[i][j] = dp[i-1][j-1]+1 (A[i-1]==B[j-1])
所有数值初始化为0,可以举例
*/
int result = 0;
int[][] dp = new int[nums1.length+1][nums2.length+1];//注意需要+1 考虑最后一位
for(int i = 1;i<=nums1.length;i++){
for(int j = 1;j<=nums2.length;j++){
if(nums1[i-1] == nums2[j-1]){
dp[i][j] = dp[i-1][j-1] + 1;
}
result = Math.max(dp[i][j],result);
///System.out.print(" "+dp[i][j]);
}
}
return result;
}
}
1143 最长公共子序列–不连续
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jtoDAyWt-1649300405807)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220107110404557.png)]
【示例】
输入:text1 = "abcde", text2 = "ace"
输出:3
解释:最长公共子序列是 "ace" ,它的长度为 3 。
【解答】
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
/**
dp[i][j]:text1下标为i-1之前的与text2下标为j-1之前的字符串最长公共子序列长度
当text1[i-1]==text2[j-1] dp[i][j] = dp[i-1][j-1] + 1
当text1[i-1]!=text2[j-1] dp[i][j] = max{dp[i-1][j],dp[i][j-1]} 有递归的感觉
*/
int[][] dp = new int[text1.length()+1][text2.length()+1];
for(int i = 1;i<=text1.length();i++){
for(int j = 1;j<=text2.length();j++){
if(text1.charAt(i-1) == text2.charAt(j-1)){
dp[i][j] = dp[i-1][j-1] + 1;
}else{
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[text1.length()][text2.length()];
}
}
1035 不相交的线
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LsRhqaat-1649300405807)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220107112736274.png)]
【示例】

输入:nums1 = [1,4,2], nums2 = [1,2,4]
输出:2
解释:可以画出两条不交叉的线,如上图所示。
但无法画出第三条不相交的直线,因为从 nums1[1]=4 到 nums2[2]=4 的直线将与从 nums1[2]=2 到 nums2[1]=2 的直线相交。
【解答】
class Solution {
public int maxUncrossedLines(int[] nums1, int[] nums2) {
/**
dp[i][j]:nums[0..i-1]与nums[0...j-1]最长公共子序列长度
当nums1[i-1] == nums2[j-1] dp[i][j] = dp[i-1][j-1] + 1
当nums1[i-1] != nums2[j-1] dp[i][j] = max{dp[i-1][j],dp[i][j-1]}
*/
int[][] dp = new int[nums1.length+1][nums2.length+1];
for(int i = 1;i<=nums1.length;i++){
for(int j = 1;j<=nums2.length;j++){
if(nums1[i-1] == nums2[j-1]){
dp[i][j] = dp[i-1][j-1] + 1;
}else{
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[nums1.length][nums2.length];
}
}
53 最大子数组和
【题目】
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
【示例】
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
【解答】
贪心|动态
class Solution {
public int maxSubArray(int[] nums) {
/**
dp[i]:包含下标i在内的最大子数组和
dp[i] = max{dp[i-1]+nums[i],nums[i]}//要么当前数值加在前面,要么从头开始
*/
int[] dp = new int[nums.length];
dp[0] = nums[0];
int result = nums[0];
for(int i = 1;i<nums.length;i++){
dp[i] = Math.max(dp[i-1]+nums[i],nums[i]);
result = Math.max(result,dp[i]);
}
return result;
}
}
392 判断子序列
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uyW05Mvm-1649300405807)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220107160817704.png)]
【示例】
输入:s = "abc", t = "ahbgdc"
输出:true
输入:s = "axc", t = "ahbgdc"
输出:false
【解答】
class Solution {
public boolean isSubsequence(String s, String t) {
/**
dp[i][j]:s的以i-1下标结尾的字符串与t的以j-1结尾的字符串的最长子序列长度
if s[i-1] == t[j-1] dp[i][j] = dp[i-1][j-1] + 1
else dp[i][j] = dp[i][j-1]
*/
int[][] dp = new int[s.length()+1][t.length()+1];
for(int i = 1;i<=s.length();i++){
for(int j = 1;j<=t.length();j++){
if(s.charAt(i-1) == t.charAt(j-1)){
dp[i][j] = dp[i-1][j-1] + 1;
}else{
dp[i][j] = dp[i][j-1];
}
}
}
if(dp[s.length()][t.length()] == s.length()){
return true;
}else{
return false;
}
}
}
115 不同的子序列
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L7MhlDBr-1649300405808)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220107170224902.png)]
【示例】
输入:s = "babgbag", t = "bag"
输出:5
解释:
如下图所示, 有 5 种可以从 s 中得到 "bag" 的方案。
babgbag
babgbag
babgbag
babgbag
babgbag
【解答】
class Solution {
public int numDistinct(String s, String t) {
/**
dp[i][j]:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]
if s[i-1] == t[j-1]
dp[i][j] = dp[i-1][j-1](用s[i-1]来匹配) + dp[i-1][j](不用s[i-1]匹配)
else dp[i][j] = dp[i-1][j](不用s[i-1]匹配)
初始化:当t为空字符串时 即dp[i][0] = 1
当s为空字符串时 即dp[0][j] = 0
dp[0][0] = 1
*/
int[][] dp = new int[s.length()+1][t.length()+1];
for(int i = 0;i<=s.length();i++){
dp[i][0] = 1;
}
for(int i = 1;i<=s.length();i++){
for(int j = 1;j<=t.length();j++){
if(s.charAt(i-1) == t.charAt(j-1)){
dp[i][j] = dp[i-1][j-1] + dp[i-1][j];
}else{
dp[i][j] = dp[i-1][j];
}
}
}
return dp[s.length()][t.length()];
}
}
583 两个字符串的删除操作
【题目】
给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。
【示例】
输入: "sea", "eat"
输出: 2
解释: 第一步将"sea"变为"ea",第二步将"eat"变为"ea"
【解答】
class Solution {
public int minDistance(String word1, String word2) {
/**
dp[i][j]:以i-1结尾的字符串word1和以j-1的字符串word2相同时,需要的删除操作最小次数
若word1[i-1] == word2[j-1] 无需操作 dp[i][j] = dp[i-1][j-1]
若 != 三种情况
删除word1[i-1] dp[i-1][j] + 1
删除word2[j-1] dp[i][j-1] + 1
删除word1[i-1]和word2[j-1] + 2
初始化 dp[i][0] = i
dp[0][j] = j
*/
int[][] dp= new int[word1.length()+1][word2.length()+1];
for(int i = 1;i<=word1.length();i++){
dp[i][0] = i;
}
for(int j = 1;j<=word2.length();j++){
dp[0][j] = j;
}
for(int i = 1;i<=word1.length();i++){
for(int j = 1;j<=word2.length();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],dp[i][j-1])+1,dp[i-1][j-1]+2);
}
}
}
return dp[word1.length()][word2.length()];
}
}
72 编辑距离
【题目】
给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
插入一个字符
删除一个字符
替换一个字符
【示例】
输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')
输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')
【解答】1
class Solution {
public int minDistance(String word1, String word2) {
/**
dp[i][j]:以i-1结尾的word1和以j-1结尾的word2,最少操作次数
if word1[i-1] == word2[j-1] dp[i][j] = dp[i-1][j-1]
else !=
删除word1的i-1(相当于word2增加一个字符) dp[i-1][j] + 1(删除操作)
增加word1的i-1(相当于word1删除一个字符) dp[i][j-1] + 1(增加操作)
替换一个元素 dp[i-1][j-1] + 1
*/
int[][] dp = new int[word1.length()+1][word2.length()+1];
for(int i = 1;i<=word1.length();i++){
dp[i][0] = i;
}
for(int j = 1;j<=word2.length();j++){
dp[0][j] = j;
}
for(int i = 1;i<=word1.length();i++){
for(int j = 1;j<=word2.length();j++){
if(word1.charAt(i-1) == word2.charAt(j-1)){
dp[i][j] = dp[i-1][j-1];
}else{
dp[i][j] = Math.min(dp[i-1][j],Math.min(dp[i][j-1],dp[i-1][j-1]))+1;
}
}
}
return dp[word1.length()][word2.length()];
}
}
647 回文子串
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oW4NDdqo-1649300405808)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220107173508849.png)]
【示例】
输入:s = "abc"
输出:3
解释:三个回文子串: "a", "b", "c"
输入:s = "aaa"
输出:6
解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"
【解答】
class Solution {
public int countSubstrings(String s) {
/**
dp[i][j]:[i,...,j]是不是回文字符串
if(s[i] == s[j]) {
如果长度是1 如“a” 那结果++ dp[i][j] = true
如果长度是2 如“aa” 那结果++ dp[i][j] = true
如果长度>2 则需要看dp[i+1][j-1] 若是true 则结果++ dp[i][j] = true
}
*/
boolean[][] dp = new boolean[s.length()][s.length()];
int result = 0;
for(int i = s.length()-1;i>=0;i--){
for(int j = i;j<s.length();j++){//注意j要从i开始!
if(s.charAt(i) == s.charAt(j)){
if(j-i+1 <= 2){
result++;
dp[i][j] = true;
}else if(dp[i+1][j-1]){
result++;
dp[i][j] = true;
}
}
}
}
return result;
}
}
516 最长回文子序列
【题目】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZJ1MqUfB-1649300405808)(C:\Users\xinya\AppData\Roaming\Typora\typora-user-images\image-20220107173606171.png)]
【示例】
输入:s = "bbbab"
输出:4
解释:一个可能的最长回文子序列为 "bbbb" 。
输入:s = "cbbd"
输出:2
解释:一个可能的最长回文子序列为 "bb" 。
【解答】
class Solution {
public int longestPalindromeSubseq(String s) {
/**
dp[i][j]:[i,...,j]中回文字符串最长长度
if s[i] == s[j] dp[i][j] = dp[i+1][j-1] + 2
else dp[i][j] = max{dp[i][j-1],dp[i+1][j]}//分别把s[i]和s[j]加入当前字符串中
初始化dp[i][i] = 1
*/
int[][] dp = new int[s.length()][s.length()];
for(int i = 0;i<s.length();i++){
dp[i][i] = 1;
}
for(int i = s.length()-1;i>=0;i--){
for(int j = i+1;j<s.length();j++){
if(s.charAt(i) == s.charAt(j)){
dp[i][j] = dp[i+1][j-1] + 2;
}else{
dp[i][j] = Math.max(dp[i][j-1],dp[i+1][j]);
}
}
}
return dp[0][s.length()-1];
}
}