力扣[LeetCode]-面试150Java代码实现

力扣[LeetCode]-面试150Java代码实现

文章目录

  • 力扣[LeetCode]-面试150Java代码实现
  • 数组、字符串篇
    • 1.合并两个有序数组
    • 2.移除元素
    • 3.删除有序数组中的重复项
    • 4.删除有序数组中的重复项 II
    • 5.多数元素
    • 6.轮转数组
    • 7.买卖股票的最佳时机
    • 8.买卖股票的最佳时机 II
    • 9.跳跃游戏
    • 10.跳跃游戏II
    • 11.H 指数
    • 12.O(1)时间插入、删除和获取随机元素
    • 13.除自身以外数组的乘积
    • 14.加油站
    • 15.分发糖果
    • 16.接雨水
    • 17.罗马数字转整数
    • 18.整数转罗马数字
    • 19.最后一个单词的长度
    • 20.最长公共前缀
    • 21.反转字符串中的单词
    • 22.Z 字形变换
    • 23.找出字符串中第一个匹配项的下标
    • 24.文本左右对齐
  • 双指针
    • 25.验证回文串
    • 26.判断子序列
    • 27.两数之和|-输入有序数组
    • 28.盛最多水的容器(hot100)
    • 29.三数之和
  • 滑动窗口
    • 30.长度最小的子数组
    • 31.无重复字符的最长子串(hot100)
    • 32.串联所有单词的子串
    • 33.最小覆盖子串(hot100)
  • 矩阵
    • 34.有效的数独
    • 35.螺旋矩阵
    • 36.旋转图像
    • 37.矩阵置零
    • 38.生命游戏
  • 哈希表
    • 39.赎金信
    • 40.同构字符串
    • 41.单词规律
    • 42.有效的字母异位词
    • 43.字母异位词分组(hot100)
    • 44.两数之和
    • 45.快乐数
    • 46.存在重复元素Ⅱ
    • 47. 最长连续序列
  • 区间
    • 48.汇总区间
    • 49.合井区间(hot100)
    • 50.插入区间
    • 51.用最少数量的箭引爆气球
    • 52.有效的括号
    • 53.简化路径
    • 54.最小栈
    • 55.逆波兰表达式求值
    • 56.基本计算器
  • 链表
    • 57.环形链表(hot100)
    • 58.两数相加(hot100)
    • 59.合并两个有序链表(hot100)
    • 60.随机链表的复制(hot100)
    • 61.反转链表 II
    • 62.K个一组翻转链表
    • 63.删除链表的倒数第 N个结点
    • 64.删除排序链表中的重复元素 II
    • 65.旋转链表
    • 66.分隔链表
    • 67.LRU 缓存(hot100)
  • 二叉树
    • 68.二叉树的最大深度(hot100)
    • 69.相同的树
    • 70.翻转二叉树(hot100)
    • 71.对称二叉树(hot100)
    • 72.从前序与中序遍历序列构造二叉树(hot100)
    • 73.从中序与后序遍历序列构造二叉树
    • 74.填充每个节点的下一个右侧节点指针II


数组、字符串篇

1.合并两个有序数组

88.合并两个有序数组
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。

请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。

注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int p = m+n-1;
        //m是长度,m-1是索引
        m--;
        n--;
        while(m>=0&&n>=0){
            if(nums1[m]>=nums2[n]){
                nums1[p]=nums1[m];
                m--;
            }else{
                nums1[p]=nums2[n];
                n--;
            }
            p--;    
        }
        //nums2里面的比较小,n还不为0
        while(n>=0){
            nums1[p--]=nums2[n--];
        }
    }
}

2.移除元素

27.移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。

假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:

更改 nums 数组,使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。
返回 k。

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2,,]
解释:你的函数函数应该返回 k = 2, 并且 nums 中的前两个元素均为 2。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。

class Solution {
    public int removeElement(int[] nums, int val) {
        int k = 0;
        //双指针
        int left=0,right=0;
        while(right<nums.length&&left<=right){
            if(nums[right]!=val){
                //不相等时进行元素覆盖
                nums[left] = nums[right];
                left++;
                k++;
            }
            right++;
        }
        return k;
    }
}

优化:避免对不相等的元素也进行复制

class Solution {
    public int removeElement(int[] nums, int val) {
        //双指针
        int left=0,right=nums.length-1;
        while(left<=right){
            if(nums[left]==val){
                //left与right指向的值交换
                int temp = nums[left];
                nums[left] = nums[right];
                nums[right] = temp;
                right--;
            }else{
                left++;
            }
        }
        //left前面的都是不相等的
        return left;
    }
}

3.删除有序数组中的重复项

26.删除有序数组中的重复项
给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。

考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:

更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
返回 k 。

输入:nums = [1,1,2]
输出:2, nums = [1,2,_]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。

class Solution {
    public int removeDuplicates(int[] nums) {
        int left=1,right=1;
        //双指针
        while(right<nums.length&&left<=right){
            //比较left-1和right值不相等就覆盖
            if(nums[left-1]!=nums[right]){
                nums[left]=nums[right];
                left++;
            }
            right++;
        }
        //left
        return left;
    }
}

4.删除有序数组中的重复项 II

80.删除有序数组中的重复项 I
给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

输入:nums = [1,1,1,2,2,3]
输出:5, nums = [1,1,2,2,3]
解释:函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3。 不需要考虑数组中超出新长度后面的元素。

class Solution {
    public int removeDuplicates(int[] nums) {
        int left=2,right=2;
        while(right<nums.length&&left<=right){
            if(nums[left-2]!=nums[right]){
                nums[left]=nums[right];
                left++;
            }
            right++;
        }
        return left;
    }
}

5.多数元素

169.多数元素
给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

class Solution {
    public int majorityElement(int[] nums) {
        int curr = 0;
        int count =0;
        //摩尔投票
        for(int num:nums){
            if(count==0){
                curr = num;
                count++;
            }else if(curr==num){
                count++;
            }else{
                count--;
            }
        }
        return curr;
    }
}

6.轮转数组

189.轮转数组

class Solution {
    public void rotate(int[] nums, int k) {
        int n = nums.length-1;
        k = k%(n+1);//避免k大于nums长度
        reverse(nums,0,n);//整个翻转
        reverse(nums,0,k-1);//翻转前k
        reverse(nums,k,n);//翻转k后面的
    }
    public void reverse(int[] nums, int start, int end){
        while(start<=end){
            int temp = nums[end];
            nums[end] = nums[start];
            nums[start] = temp;
            start++;
            end--;
        }
    }
}

7.买卖股票的最佳时机

121.买卖股票的最佳时机
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

一次遍历

public int maxProfit(int[] prices) {
        int maxP = 0;
        int buy = prices[0];
        for(int i=1;i<prices.length;i++){
            if(prices[i]<buy){
                buy = prices[i];
            }else{
                maxP = Math.max(maxP, prices[i]-buy);
            }
        }
        return maxP;
    }

动态规划

public int maxProfit(int[] prices) {
        int[] dp = new int[prices.length];
        int buy = prices[0];
        dp[0] = 0;
        for(int i=1;i<prices.length;i++){
            buy = Math.min(prices[i],buy);
            dp[i] = Math.max(dp[i-1], prices[i]-buy);
        }
        return dp[prices.length-1];
    }

8.买卖股票的最佳时机 II

122.买卖股票的最佳时机 II
给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润 。

输入:prices = [7,1,5,3,6,4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3。
最大总利润为 4 + 3 = 7 。

动态规划

public int maxProfit(int[] prices) {
        int[][] dp = new int[prices.length][2];
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        //0是不持有股票,1是持有股票
        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][0]-prices[i], dp[i-1][1]);
        }
        return Math.max(dp[prices.length-1][0], dp[prices.length-1][1]);       
    }

贪心算法

public int maxProfit(int[] prices) {
        int res = 0;
        for(int i=1;i<prices.length;i++){
            res += Math.max(0, prices[i]-prices[i-1]);
        }
        return res;       
    }

9.跳跃游戏

55.跳跃游戏
给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。

class Solution {
    public boolean canJump(int[] nums) {
        int maxCover = 0;
        //在maxCover范围内跳跃
        for(int i=0;i<=maxCover;i++){
            maxCover = Math.max(maxCover, i+nums[i]);
            if(maxCover>=nums.length-1){
                return true;
            }
        }
        return false;
    }
}

10.跳跃游戏II

45.跳跃游戏II
给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。

每个元素 nums[i] 表示从索引 i 向后跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:

0 <= j <= nums[i]
i + j < n
返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]。

class Solution {
    public int jump(int[] nums) {
        int cover = 0;
        int end = 0;
        int count = 0;
        //如果访问最后一个元素,在边界正好为最后一个位置的情况下,我们会增加一次「不必要的跳跃次数」,因此我们不必访问最后一个元素。
        for(int i=0;i<nums.length-1;i++){
            cover = Math.max(cover, i+nums[i]);
            if(i==end){//边界
                count++;
                end = cover;
            }
        }
        return count;
    }
}

11.H 指数

274.H 指数
给你一个整数数组 citations ,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 h 指数。

根据维基百科上 h 指数的定义:h 代表“高引用次数” ,一名科研人员的 h 指数 是指他(她)至少发表了 h 篇论文,并且 至少 有 h 篇论文被引用次数大于等于 h 。如果 h 有多种可能的值,h 指数 是其中最大的那个。

class Solution {
    public int hIndex(int[] citations) {
        Arrays.sort(citations);
        int h = 0;
        int n = citations.length;
        for(int i=n-1;i>=0;i--){
        	//满足h
            if(h<citations[i]){
                h++;
            }
        }
        return h;
    }
}

12.O(1)时间插入、删除和获取随机元素

O(1)时间插入、删除和获取随机元素
实现RandomizedSet 类:

RandomizedSet() 初始化 RandomizedSet 对象
bool insert(int val) 当元素 val 不存在时,向集合中插入该项,并返回 true ;否则,返回 false 。
bool remove(int val) 当元素 val 存在时,从集合中移除该项,并返回 true ;否则,返回 false 。
int getRandom() 随机返回现有集合中的一项(测试用例保证调用此方法时集合中至少存在一个元素)。每个元素应该有 相同的概率 被返回。
你必须实现类的所有函数,并满足每个函数的 平均 时间复杂度为 O(1) 。

class RandomizedSet {
    //变长数组 + 哈希表
    List<Integer> list;
    Map<Integer,Integer> map;
    public RandomizedSet() {
        list = new ArrayList<>();
        map = new HashMap<>();
    }
    
    public boolean insert(int val) {
        if(!map.containsKey(val)){
            int index = list.size();
            map.put(val,index);
            list.add(val);
            return true;
        }else{
            return false;
        }
    }
    
    public boolean remove(int val) {
        if(map.containsKey(val)){
            int index = map.get(val);
            int n = list.size();
            //最后一个元素值与当前值交换
            int lastVal = list.get(n-1);
            list.set(index,lastVal);
            list.removeLast();
            //map也交换,后删除最后一个元素
            map.put(lastVal,index);
            map.remove(val);
            return true;
        }else{
            return false;
        }
    }
    public int getRandom() {
        Random rd = new Random();
        int index = rd.nextInt(list.size());
        return list.get(index);
    }
}

13.除自身以外数组的乘积

238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。

题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。

请 不要使用除法,且在 O(n) 时间复杂度内完成此题。

class Solution {
    public int[] productExceptSelf(int[] nums) {
        int[] left = new int[nums.length];
        int[] right = new int[nums.length];
        int[] res = new int[nums.length];
        left[0] = nums[0];
        right[nums.length-1] = nums[nums.length-1];
        for(int i=1;i<nums.length;i++){
            left[i] = left[i-1]*nums[i];
        }
        for(int i=nums.length-2;i>=0;i--){
            right[i] =right[i+1]* nums[i];
        }
        res[0] = right[1];
        res[nums.length-1] = left[nums.length-2];
        for(int i=1;i<nums.length-1;i++){
            res[i] = left[i-1]*right[i+1];
        }
        return res;
    }
}

14.加油站

134.加油站
在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -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 可为起始索引。

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int n = gas.length;
        int k=0;
        while(k<n){
            int curr = 0;
            int currgas = 0;
            while(curr<n){
                int i = (k+curr)%n;
                currgas = currgas + gas[i] - cost[i];
                if(currgas<0){
                    break;
                }
                curr++;
            }
            if(curr==n){
                return k;
            }else{
                //k-k+curr无法到达,下次从k+curr+1开始计算
                k = k+curr+1;
            }
        }
        return -1;
    }
}

15.分发糖果

135.分发糖果
n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。

你需要按照以下要求,给这些孩子分发糖果:

每个孩子至少分配到 1 个糖果。
相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。

两次遍历:

class Solution {
    public int candy(int[] ratings) {
        int len = ratings.length;
        int count = 0;
        int[] left =new int[len];
        for(int i=0;i<len;i++){
            if(i>0&&ratings[i-1]<ratings[i]){
                left[i] = left[i-1]+1;
            }else{
                left[i] =1;
            }
        }
        int right=0;
        for(int i=len-1;i>=0;i--){
            if(i<len-1&&ratings[i+1]<ratings[i]){
                right++;
            }else{
                right=1;
            }
            //最大值同时满足左右规则
            count+=Math.max(left[i],right);
        }
        return count;
    }
}

一次遍历:

class Solution {
    public int candy(int[] ratings) {
        int n = ratings.length;
        int count = 1;
        int pre = 1;
        int inc = 1,dec = 0;
        for(int i=1;i<n;i++){
            if(ratings[i-1]<=ratings[i]){
                dec=0;
                pre=ratings[i] == ratings[i - 1] ? 1 : pre + 1;
                inc=pre;
                count+=pre;
            }else{
                dec++;
                if (dec == inc) {
                    dec++;
                }
                count+=dec;
                pre=1;
            }
        }
        return count;
    }
}

16.接雨水

42.接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

首先按行求,会超时

class Solution {
    //会超时
    public int trap(int[] height) {
        //按行求,需要找出有几行
        int maxNum = getMax(height);
        int count = 0;
        for(int i=1;i<=maxNum;i++){
            int temp = 0;
            boolean start = false;
            for(int j=0;j<height.length;j++){
                if(height[j]<i){
                    if(start) temp++;
                }else{
                    count+=temp;
                    temp=0;
                    //只有存在墙才会开始存水
                    start=true;
                }
            }
        }
        return count;
    }
    public int getMax(int[] height){
        int maxNum = height[0];
        for(int i=0;i<height.length;i++){
            maxNum = Math.max(maxNum,height[i]);
        }
        return maxNum;
    }
}

按列求,也会超时

class Solution {
    public int trap(int[] height) {
        //按列求
        int count = 0;
        int leftH = 0;
        int rightH = 0;
        for(int i=1;i<height.length-1;i++){
            leftH = getMax(height,0,i);
            rightH = getMax(height,i+1,height.length);
            int minH = Math.min(leftH,rightH);
            if(height[i]<minH){
                count+=minH-height[i];
            }
        }
        return count;
    }
    public int getMax(int[] height,int start,int end){
        int maxNum = height[start];
        for(int i=start;i<end;i++){
            maxNum = Math.max(maxNum,height[i]);
        }
        return maxNum;
    }
}

存在数组里面,动态规划,可AC

class Solution {
    public int trap(int[] height) {
        //按列求
        int count = 0;
        //保存左边和右边最高的墙(不包括自身)
        int[] leftH = new int[height.length];
        int[] rightH = new int[height.length];
        leftH[0]=0;
        rightH[height.length-1]=0;
        for(int i=1;i<height.length;i++){
            leftH[i] = Math.max(leftH[i-1],height[i-1]);
        }
        for(int i=height.length-2;i>=0;i--){
            rightH[i] = Math.max(rightH[i+1],height[i+1]);
        }
        for(int i=1;i<height.length-1;i++){
            int minH = Math.min(leftH[i],rightH[i]);
            if(height[i]<minH){
                count+=minH-height[i];
            }
        }
        return count;
    }
}

双指针

class Solution {
    public int trap(int[] height) {
        //按列求
        int count = 0;
        //双指针
        int left=1,right=height.length-2;
        int max_left=height[left-1],max_right=height[right+1];
        while(left<=right){
            max_left = Math.max(max_left, height[left]);
            max_right = Math.max(max_right, height[right]);
            if(max_left<=max_right){
                //左指针的右边至少有一个板子 > 左指针左边所有板子
                count+=max_left-height[left];
                left++;
            }else{
                count+=max_right-height[right];
                right--;
            }
        }
        return count;
    }
}

双指针简化:

class Solution {
    public int trap(int[] height) {
        int total=0;
        int left=0;
        int right=height.length-1;
        int maxLeft=0;
        int maxRight=0;
        while(left<right){
            if(height[left]<=height[right]){
                //左指针处积水量由maxLeft决定,右侧存在足够高的墙
                total+=Math.max(0,maxLeft-height[left]);
                //先计算total再更新maxLeft
                maxLeft=Math.max(maxLeft,height[left]);
                left++;
            }else{
                total+=Math.max(0,maxRight-height[right]);
                maxRight=Math.max(maxRight,height[right]);
                right--;
            }
        }
        return total;
        
    }
}

17.罗马数字转整数

13.罗马数字转整数
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。

字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1 。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。

通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:

I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数。

class Solution {
    public int romanToInt(String s) {
        Map<Character,Integer> map = new HashMap<>();
        map.put('I',1);
        map.put('V',5);
        map.put('X',10);
        map.put('L',50);
        map.put('C',100);
        map.put('D',500);
        map.put('M',1000);
        char[] chars = s.toCharArray();
        int count = 0;
        for(int i=0;i<s.length()-1;i++){
            int curr = map.get(chars[i]);
            int next = map.get(chars[i+1]);
            if(curr<next){
                count-=curr;
            }else{
                count+=curr;
            }
        }
        count+=map.get(chars[s.length()-1]);
        return count;
    }
}

18.整数转罗马数字

12.整数转罗马数字
七个不同的符号代表罗马数字,其值如下:

符号 值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
罗马数字是通过添加从最高到最低的小数位值的转换而形成的。将小数位值转换为罗马数字有以下规则:

如果该值不是以 4 或 9 开头,请选择可以从输入中减去的最大值的符号,将该符号附加到结果,减去其值,然后将其余部分转换为罗马数字。
如果该值以 4 或 9 开头,使用 减法形式,表示从以下符号中减去一个符号,例如 4 是 5 (V) 减 1 (I): IV ,9 是 10 (X) 减 1 (I):IX。仅使用以下减法形式:4 (IV),9 (IX),40 (XL),90 (XC),400 (CD) 和 900 (CM)。
只有 10 的次方(I, X, C, M)最多可以连续附加 3 次以代表 10 的倍数。你不能多次附加 5 (V),50 (L) 或 500 (D)。如果需要将符号附加4次,请使用 减法形式。
给定一个整数,将其转换为罗马数字。

class Solution {
    public String intToRoman(int num) {
        StringBuilder sb = new StringBuilder();
        Map<Integer,Character> map = new HashMap<>();
        map.put(1,'I');
        map.put(5,'V');
        map.put(10,'X');
        map.put(50,'L');
        map.put(100,'C');
        map.put(500,'D');
        map.put(1000,'M');

        for(int i=0;i<4;i++){
            int pow = (int)Math.pow(10,3-i);
            int number = num/pow%10;
            if(number>=0&&number<4){
                while(number!=0){
                    sb.append(map.get(pow));
                    number--;
                }
            }else if(number==4){
                sb.append(map.get(pow));
                sb.append(map.get(5*pow));
            }else if (number>=5&&number<9){
                sb.append(map.get(5*pow));
                number-=5;
                while(number!=0){
                    sb.append(map.get(pow));
                    number--;
                }
            }else{
                sb.append(map.get(pow));
                sb.append(map.get(10*pow));
            }
        }
        return sb.toString();        
    }
}

优化:

class Solution {
    public String intToRoman(int num) {
        StringBuilder sb = new StringBuilder();
        int[] values = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
    String[] symbols = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
        for(int i=0;i<values.length;i++){
            int value = values[i];
            while(num>=value){
                sb.append(symbols[i]);
                num-=value;
            }
            if(num==0){
                break;
            }
        }
        return sb.toString();        
    }
}

19.最后一个单词的长度

58.最后一个单词的长度
给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。

单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。

class Solution {
    public int lengthOfLastWord(String s) {
        String[] strs = s.split(" ");
        return strs[strs.length-1].length();
    }
}

20.最长公共前缀

14.最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 “”。

横向扫描

class Solution {
    public String longestCommonPrefix(String[] strs) {
        String res = strs[0];
        int num = strs.length;
        //横向扫描
        for(int i=1;i<num;i++){
            res = longestTwoCommonPrefix(res,strs[i]);
            if(res.length==0){
                break;
            }
        }
        return res;
    }
    public String longestTwoCommonPrefix(String s1, String s2) {
        int len = Math.min(s1.length(),s2.length());
        StringBuilder sb = new StringBuilder();
        for(int i=0;i<len;i++){
            if(s1.charAt(i)==s2.charAt(i)){
                sb.append(s1.charAt(i));
            }else{
                break;
            }
        }
        return sb.toString();
    }
}

21.反转字符串中的单词

151.反转字符串中的单词
给你一个字符串 s ,请你反转字符串中 单词 的顺序。

单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。

返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。

注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。

class Solution {
    public String reverseWords(String s) {
        String[] strs = s.split(" ");
        StringBuilder sb = new StringBuilder();
        List<String> list = new ArrayList<>();
        //去除空格
        for(int i=0;i<strs.length;i++){
            if(strs[i]!=""&&strs[i]!=" "){
                list.add(strs[i]);
            }
        }
        //组合
        for(int i=list.size()-1;i>=0;i--){
            sb.append(list.get(i));
            if(i>0){
                sb.append(" ");
            }
        }
        return sb.toString();
    }
}

优化:

class Solution {
    public String reverseWords(String s) {
        String[] strs = s.split(" ");
        List<String> list = new ArrayList<>();
        //去除空格
        for(int i=0;i<strs.length;i++){
            if(strs[i]!=""&&strs[i]!=" "){
                list.add(strs[i]);
            }
        }
        //翻转
        Collections.reverse(list);
        return String.join(" ",list);
    }
}

22.Z 字形变换

6.Z 字形变换
将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。

比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下:

P A H N
A P L S I I G
Y I R
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:“PAHNAPLSIIGYIR”。

请你实现这个将字符串进行指定行数变换的函数:

string convert(string s, int numRows);

class Solution {
    public String convert(String s, int numRows) {
        if(numRows<=1) return s;
        List<StringBuilder> rows = new ArrayList<StringBuilder>();
        for(int i =0;i<numRows;i++){
            rows.add(new StringBuilder());
        }
        int index = 0,flag=-1;
        for(char c:s.toCharArray()){
            rows.get(index).append(c);
            if(index==0||index==numRows-1){
                flag = -flag;//flag变向
            }
            index+=flag;
        }
        StringBuilder res = new StringBuilder();
        for(int i =0;i<numRows;i++){
            res.append(rows.get(i));
        }
        return res.toString();
    }
}

23.找出字符串中第一个匹配项的下标

28.找出字符串中第一个匹配项的下标
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。

暴力解法:

class Solution {
    public int strStr(String haystack, String needle) {
        int n = haystack.length(), m = needle.length();
        for(int i=0;i<n-m+1;i++){
            boolean flage = true;
            for(int j=0;j<m;j++){
                if(haystack.charAt(i+j)!=needle.charAt(j)){
                    flage = false;
                    break;
                }
            }
            if(flage){
                return i;
            }
        }
        return -1;
        
    }
}

KMP 算法

class Solution {
    public int strStr(String haystack, String needle) {
        int n = haystack.length(), m = needle.length();
        int[] next = new int[m];
        for(int i=1,j=0;i<m;i++){
            while(j>0&&needle.charAt(i)!=needle.charAt(j)){
                j=next[j-1];
            }
            if(needle.charAt(i)==needle.charAt(j)){
                j++;
            }
            next[i] = j;
        }
        for(int i=0,j=0;i<n;i++){
            while (j>0&&haystack.charAt(i)!=needle.charAt(j)) {
                j=next[j-1];
            }
            if (haystack.charAt(i)==needle.charAt(j)) {
                j++;
            }
            if(j==m){
                return i-j+1;
            }
        }
        return -1;     
    }
}

24.文本左右对齐

68.文本左右对齐
给定一个单词数组 words 和一个长度 maxWidth ,重新排版单词,使其成为每行恰好有 maxWidth 个字符,且左右两端对齐的文本。

你应该使用 “贪心算法” 来放置给定的单词;也就是说,尽可能多地往每行中放置单词。必要时可用空格 ’ ’ 填充,使得每行恰好有 maxWidth 个字符。

要求尽可能均匀分配单词间的空格数量。如果某一行单词间的空格不能均匀分配,则左侧放置的空格数要多于右侧的空格数。

文本的最后一行应为左对齐,且单词之间不插入额外的空格。

注意:

单词是指由非空格字符组成的字符序列。
每个单词的长度大于 0,小于等于 maxWidth。
输入单词数组 words 至少包含一个单词。

class Solution {
    public List<String> fullJustify(String[] words, int maxWidth) {
        List<String> res = new ArrayList<>();
        int right=0, n = words.length;
        while(true){
            int left = right;
            int sumLen = 0;
            while(right<n&&sumLen+words[right].length()+right-left<=maxWidth){
                sumLen+=words[right].length();
                right++;
            }
            // 当前行是最后一行:单词左对齐,且单词之间应只有一个空格,在行末填充剩余空格
            if(right==n){
                StringBuilder sb = new StringBuilder();
                for(int i=left;i<right;i++){
                    sb.append(words[i]);
                    if(i!=right-1){
                        sb.append(" ");
                    }
                }
                int len = sb.length();
                for(int i=len;i<maxWidth;i++){
                    sb.append(" ");
                }
                res.add(sb.toString());
                return res;
            }
            int numWords = right - left;
            int numSpaces = maxWidth - sumLen;
            // 当前行只有一个单词:该单词左对齐,在行末填充剩余空格
            if(numWords==1){
                StringBuilder sb = new StringBuilder();
                sb.append(words[left]);
                int len = sb.length();
                for(int i=len;i<maxWidth;i++){
                    sb.append(" ");
                }
                res.add(sb.toString());
                continue;
            }
            // 当前行不只一个单词,将空格均匀分配在单词之间
            int avgSpaces = numSpaces / (numWords - 1);
            int extraSpaces = numSpaces % (numWords - 1);
            StringBuilder sb = new StringBuilder();
            前extraSpaces个单词之间填充avgSpaces+1个空格
            for(int i=left;i<left+extraSpaces;i++){
                sb.append(words[i]);
                for(int j=0;j<avgSpaces+1;j++){
                    sb.append(" ");
                }
            }
            //其余单词之间填充avgSpaces个空格
            for(int i=left+extraSpaces;i<right;i++){
                sb.append(words[i]);
                if(i!=right-1){
                    for(int j=0;j<avgSpaces;j++){
                        sb.append(" ");
                    }
                }
                
            }
            res.add(sb.toString());
        }
    }
}

双指针

25.验证回文串

125.验证回文串
如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。

字母和数字都属于字母数字字符。

给你一个字符串 s,如果它是 回文串 ,返回 true ;否则,返回 false 。

class Solution {
    public boolean isPalindrome(String s) {
        StringBuilder temp = new StringBuilder();
        for(char c: s.toCharArray()){
            //字母或数字
            if(Character.isLetterOrDigit(c)){
                temp.append(Character.toLowerCase(c));
            }
        }
        String str1 = temp.toString();
        String str2 = temp.reverse().toString();
        return str1.equals(str2);
    }
}

双指针

class Solution {
    public boolean isPalindrome(String s) {
        int left=0,right=s.length()-1;
        //双指针
        while(left<right){
            while(left<right&&!Character.isLetterOrDigit(s.charAt(left))){
                left++;
            }
             while(left<right&&!Character.isLetterOrDigit(s.charAt(right))){
                right--;
            }
            if(left<right&&Character.toLowerCase(s.charAt(left))!=Character.toLowerCase(s.charAt(right))){
                return false;
            }
            left++;
            right--;
        }
        return true;
    }
}

26.判断子序列

392.判断子序列
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。

进阶:

如果有大量输入的 S,称作 S1, S2, … , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?

双指针

class Solution {
    public boolean isSubsequence(String s, String t) {
        int i=0,j=0;
        for(;i<s.length()&&j<t.length();j++){
            if(s.charAt(i)==t.charAt(j)){
                i++;
            }
        }
        return i==s.length();
    }
}

27.两数之和|-输入有序数组

167.两数之和|-输入有序数组
给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。

以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。

你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。

你所设计的解决方案必须只使用常量级的额外空间。

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int[] res = new int[2];
        int i=0,j=numbers.length-1;
        while(i<j){
            //双指针
            int sum = numbers[i]+numbers[j];
            if(sum==target){
                res[0]=i+1;
                res[1]=j+1;
                break;
            }else if(sum<target){
                i++;
            }else{
                j--;
            }
        }
        return res;
    }
}

28.盛最多水的容器(hot100)

11.盛最多水的容器
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

说明:你不能倾斜容器。

class Solution {
    public int maxArea(int[] height) {
        int maxA = 0;
        int left=0,right=height.length-1;
        while(left<right){
            int w = right-left;
            int h = Math.min(height[left],height[right]);
            maxA = Math.max(maxA,w*h);
            if(height[left]<height[right]){
                left++;
            }else{
                right--;
            }
        }
        return maxA;
    }
}

29.三数之和

15.三数之和
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> res = new ArrayList<>();
        for(int i=0;i<nums.length-2;i++){
            //跳过重复元素
            if(i>0&&nums[i]==nums[i-1]) continue;
            int left=i+1,right=nums.length-1;
            while(left<right){
                int sum = nums[i]+nums[left]+nums[right];
                if(sum==0){
                    List<Integer> temp = new ArrayList<>();
                    temp.add(nums[i]);
                    temp.add(nums[left]);
                    temp.add(nums[right]);
                    res.add(temp);
                    //跳过重复元素
                    while(left<right&&nums[left]==nums[left+1]) left++;
                    while(left<right&&nums[right]==nums[right-1]) right--;
                    left++;
                    right--;
                }else if(sum>0){
                    right--;
                }else {
                    left++;
                }
            }
        }
        return res;
    }
}

滑动窗口

滑动窗口统一模板

//外层循环扩展右边界,内层循环扩展左边界
for (int l = 0, r = 0 ; r < n ; r++) {
	//当前考虑的元素
	while (l <= r && check()) {//区间[left,right]不符合题意
        //扩展左边界
    }
    //区间[left,right]符合题意,统计相关信息
}

30.长度最小的子数组

209.长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其总和大于等于 target 的长度最小的 子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int minlen = nums.length+1;
        int sum=0;
        for(int left=0,right=0;right<nums.length;right++){
            sum+=nums[right];
            //尝试缩短窗口长度
            while(left<=right&&sum>=target){
                minlen = Math.min(minlen,right-left+1);
                sum-=nums[left];
                left++;
            }
        }
        if(minlen == nums.length+1){
            return 0;
        }else{
            return minlen;
        }
    }
}

31.无重复字符的最长子串(hot100)

3.无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串 的长度。

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int maxlen = 0;
        Set<Character> set = new HashSet<>();
        for(int left=0,right=0;right<s.length();right++){
            //当前元素
            char c = s.charAt(right);
            while(left<=right&&set.contains(c)){
                set.remove(s.charAt(left));
                left++;
            }
            //放元素
            set.add(c);
            maxlen=Math.max(maxlen,right-left+1);
        }
        return maxlen;
    }
}

32.串联所有单词的子串

30.串联所有单词的子串
给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。

s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。

例如,如果 words = [“ab”,“cd”,“ef”], 那么 “abcdef”, “abefcd”,“cdabef”, “cdefab”,“efabcd”, 和 “efcdab” 都是串联子串。 “acdbef” 不是串联子串,因为他不是任何 words 排列的连接。
返回所有串联子串在 s 中的开始索引。你可以以 任意顺序 返回答案。

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> res = new ArrayList<>();
        int m = words.length;
        int n = words[0].length();
        int ls = s.length();
        
        for(int i=0;i<n&&i+m*n<=ls;i++){
            Map<String, Integer> map = new HashMap<>();
            for(int j=0;j<m;j++){
                String str = s.substring(i+j*n,i+(j+1)*n);
                map.put(str,map.getOrDefault(str,0)+1);
            }
            for(int j=0;j<m;j++){
                map.put(words[j],map.getOrDefault(words[j],0)-1);
                if(map.get(words[j])==0){
                    map.remove(words[j]);
                }
            }
            if(map.isEmpty()){
                res.add(i);
            }
            for(int start=i+n;start+m*n<=ls;start+=n){
                //右边+
                String str = s.substring(start+(m-1)*n,start+m*n);
                map.put(str,map.getOrDefault(str,0)+1);
                if(map.get(str)==0){
                    map.remove(str);
                }
                //左边-
                str = s.substring(start-n,start);
                map.put(str,map.getOrDefault(str,0)-1);
                if(map.get(str)==0){
                    map.remove(str);
                }
                if(map.isEmpty()){
                    res.add(start);
                }
            }
        }
        return res;
    }
}

33.最小覆盖子串(hot100)

76.最小覆盖子串
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。

class Solution {
    public String minWindow(String s, String t) {
        int tlen = t.length();
        int slen = s.length();
        int[] list = new int[58];
        int num = 0;
        for(int i=0;i<tlen;i++){
            list[t.charAt(i)-'A']++;
        }
        int left=0,right=0;
        int minlen = s.length()+1;
        int ansL=-1,ansR=0;
        while(left<=right&&right<slen){
            int index = s.charAt(right)-'A';
            list[index]--;
            if(list[index]>=0){
                num++;
            }
            right++;
            while(num==tlen){
                //更新最小值
                if(right-left<minlen){
                    ansL = left;
                    ansR = right;
                    minlen = right-left;
                }
                index = s.charAt(left)-'A';
                list[index]++;
                if(list[index]>0){
                    num--;
                }
                left++;
            }
        }
        return ansL==-1 ? "" : s.substring(ansL, ansR);
    }
}

矩阵

34.有效的数独

36.有效的数独
请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)

注意:

一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。
空白格用 ‘.’ 表示。

class Solution {
    public boolean isValidSudoku(char[][] board) {
        int[][] rows = new int[9][9];
        int[][] colms = new int[9][9];
        int[][][] boxs = new int[3][3][9];
        for(int i=0;i<9;i++){
            for(int j=0;j<9;j++){
                char c = board[i][j];
                if(c!='.'){
                    int index = c-'0'-1;
                    rows[i][index]++;
                    colms[j][index]++;
                    boxs[i/3][j/3][index]++;
                    if(rows[i][index]>1||colms[j][index]>1||boxs[i/3][j/3][index]>1){
                        return false;
                    }
                }
            }
        }
        return true;
    }
}

35.螺旋矩阵

54.螺旋矩阵
给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
        int m = matrix.length;
        int n = matrix[0].length;
        int left=0,right=n-1,top=0,bottom=m-1;
        List<Integer> res = new ArrayList<>();
        while(left<=right&&top<=bottom){
            for(int i=left;i<=right;i++){
                res.add(matrix[top][i]);
            }
            top++;
            for(int i=top;i<=bottom;i++){
                res.add(matrix[i][right]);
            }
            right--;
            if(top<=bottom){
                for(int i=right;i>=left;i--){
                    res.add(matrix[bottom][i]);
                }
                bottom--;
            }
            if(left<=right){
                for(int i=bottom;i>=top;i--){
                    res.add(matrix[i][left]);
                }
                left++;
            }
        }
        return res;
    }
}

36.旋转图像

48.旋转图像
给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。

你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。

class Solution {
    public void rotate(int[][] matrix) {
        //递推公式:matrix_new[j][n-i-1] = matrix[i][j];
        //也就是:matrix_new[i][j] = matrix[n-j-1][i];
        //四项循环
        //temp=matrix[row][col]
        // matrix[row][col]=matrix[n−col−1][row]
        // matrix[n−col−1][row]=matrix[n−row−1][n−col−1]
        // matrix[n−row−1][n−col−1]=matrix[col][n−row−1]
        // matrix[col][n−row−1]​=temp
        int n = matrix.length;
        for(int i=0;i<n/2;i++){
            for(int j=0;j<(n+1)/2;j++){
                //(n+1)/2奇数时需要进位
                int temp = matrix[i][j];
                matrix[i][j] = matrix[n-j-1][i];
                matrix[n-j-1][i] = matrix[n-i-1][n-j-1];
                matrix[n-i-1][n-j-1] = matrix[j][n-i-1];
                matrix[j][n-i-1] = temp;
            }
        }
  
    }
}

37.矩阵置零

73.矩阵置零
给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。

class Solution {
    public void setZeroes(int[][] matrix) {
        int m = matrix.length;
        int n = matrix[0].length;
        boolean[] row = new boolean[m];
        boolean[] col = new boolean[n];
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(matrix[i][j]==0){
                    row[i]=true;
                    col[j]=true;
                }
            }
        }
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(row[i]||col[j]){
                    matrix[i][j]=0;
                }
            }
        }
    }
}

38.生命游戏

289.生命游戏
根据 百度百科 , 生命游戏 ,简称为 生命 ,是英国数学家约翰·何顿·康威在 1970 年发明的细胞自动机。

给定一个包含 m × n 个格子的面板,每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态: 1 即为 活细胞 (live),或 0 即为 死细胞 (dead)。每个细胞与其八个相邻位置(水平,垂直,对角线)的细胞都遵循以下四条生存定律:

如果活细胞周围八个位置的活细胞数少于两个,则该位置活细胞死亡;
如果活细胞周围八个位置有两个或三个活细胞,则该位置活细胞仍然存活;
如果活细胞周围八个位置有超过三个活细胞,则该位置活细胞死亡;
如果死细胞周围正好有三个活细胞,则该位置死细胞复活;
下一个状态是通过将上述规则同时应用于当前状态下的每个细胞所形成的,其中细胞的出生和死亡是 同时 发生的。给你 m x n 网格面板 board 的当前状态,返回下一个状态。

给定当前 board 的状态,更新 board 到下一个状态。

注意 你不需要返回任何东西。

class Solution {
    public void gameOfLife(int[][] board) {
        //状态:0死亡,1存活;-1之前存活现在死亡,2之前死亡现在存活
        int m = board.length;
        int n = board[0].length;
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                int num = number(board,i,j);
                if(board[i][j]==0&&num==3){
                    board[i][j] = 2;
                }else if(board[i][j]==1){
                    if(num<2||num>3){
                        board[i][j] = -1;
                    }
                }
            }
        }
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(board[i][j]>0){
                    board[i][j] = 1;
                }else{
                    board[i][j] = 0;
                }
            }
        }
    }
    public int number(int[][] board, int row, int col){
        int m = board.length;
        int n = board[0].length;
        int num = 0;
        if(board[row][col]==1){
            num--;//跳过自身
        }
        for(int i=-1;i<2;i++){
            for(int j=-1;j<2;j++){
                int r = row+i;
                int c = col+j;
                if(r>=0&&r<m&&c>=0&&c<n){
                    if(board[r][c]==1||board[r][c]==-1){
                        num++;
                    }
                }
            }
        }
        return num;
    }
}

哈希表

39.赎金信

383.赎金信
给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false 。

magazine 中的每个字符只能在 ransomNote 中使用一次。

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        int[] list = new int[26];
        for(char c:magazine.toCharArray()){
            list[c-'a']++;
        }
        for(char c:ransomNote.toCharArray()){
            list[c-'a']--;
            if(list[c-'a']<0){
                return false;
            }
        }
        return true;
    }
}

40.同构字符串

205.同构字符串
给定两个字符串 s 和 t ,判断它们是否是同构的。

如果 s 中的字符可以按某种映射关系替换得到 t ,那么这两个字符串是同构的。

每个出现的字符都应当映射到另一个字符,同时不改变字符的顺序。不同字符不能映射到同一个字符上,相同字符只能映射到同一个字符上,字符可以映射到自己本身。

class Solution {
    public boolean isIsomorphic(String s, String t) {
        Map<Character,Character> map1 = new HashMap<>();
        Map<Character,Character> map2 = new HashMap<>();
        for(int i=0;i<s.length();i++){
            char sc = s.charAt(i), tc = t.charAt(i);
            if(map1.containsKey(sc)&&map1.get(sc)!=tc||map2.containsKey(tc)&&map2.get(tc)!=sc){
                return false;
            }
            map1.put(sc,tc);
            map2.put(tc,sc);
        }
        return true;
    }
}

41.单词规律

290.单词规律
给定一种规律 pattern 和一个字符串 s ,判断 s 是否遵循相同的规律。

这里的 遵循 指完全匹配,例如, pattern 里的每个字母和字符串 s 中的每个非空单词之间存在着双向连接的对应规律。

class Solution {
    public boolean wordPattern(String pattern, String s) {
        String[] str = s.split(" ");
        if(str.length!=pattern.length()){
            return false;
        }
        Map<Character,String> map1 = new HashMap<>();
        Map<String,Character> map2 = new HashMap<>();
        for(int i=0;i<pattern.length();i++){
            char pc = pattern.charAt(i);
            String ss = str[i];
            if(map1.containsKey(pc)&&!map1.get(pc).equals(ss)||map2.containsKey(ss)&&map2.get(ss)!=(pc)){
                return false;
            }
            map1.put(pc,ss);
            map2.put(ss,pc);
        }
        return true;
    }
}

42.有效的字母异位词

242.有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的 字母异位词。

class Solution {
    public boolean isAnagram(String s, String t) {
        if(s.length()!=t.length()){
            return false;
        }
        int[] list = new int[26];
        for(char c:s.toCharArray()){
            list[c-'a']++;
        }
        for(char c:t.toCharArray()){
            list[c-'a']--;
            if(list[c-'a']<0){
                return false;
            }
        }
        return true;
    }
}

43.字母异位词分组(hot100)

49.字母异位词分组
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。

字母异位词 是由重新排列源单词的所有字母得到的一个新单词。

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        Map<String, List<String>> map = new HashMap<>();
        for(String str:strs){
            char[] chars = str.toCharArray();
            Arrays.sort(chars);
            //String sorted = chars.toString();错误,输出类似 "[C@1b6d3586"
            String sorted = new String(chars);
            List<String> list = map.getOrDefault(sorted, new ArrayList<String>());
            list.add(str);
            map.put(sorted, list);
        }
        List<List<String>> res = new ArrayList<>(map.values());
        return res;
    }
}

44.两数之和

1.两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。

你可以按任意顺序返回答案。

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer,Integer> map = new HashMap<>();
        for(int i=0;i<nums.length;i++){
            if(map.containsKey(target-nums[i])){
                return new int[]{map.get(target-nums[i]),i};
            }
            map.put(nums[i],i);
        }
        return new int[0];
    }
}

45.快乐数

202.快乐数
编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。

class Solution {
    public boolean isHappy(int n) {
        Set<Integer> set = new HashSet<>();
        while(n!=1){
            int tmp = n;
            int res = 0;
            while(tmp!=0){
                int wei = tmp%10;
                res+=wei*wei;
                tmp/=10;
            }
            n=res;
            if(set.contains(n)){
                return false;
            }
            set.add(n);
        }
        return true;
    }
}

46.存在重复元素Ⅱ

219.存在重复元素Ⅱ
给你一个整数数组 nums 和一个整数 k ,判断数组中是否存在两个 不同的索引 i 和 j ,满足 nums[i] == nums[j] 且 abs(i - j) <= k 。如果存在,返回 true ;否则,返回 false 。

class Solution {
    public boolean containsNearbyDuplicate(int[] nums, int k) {
         Map<Integer, Integer> map = new HashMap<Integer, Integer>();
         for(int i=0;i<nums.length;i++){
            if(map.containsKey(nums[i])&&i-map.get(nums[i])<=k){
                return true;
            }
            map.put(nums[i],i);
         }
         return false;
    }
}

47. 最长连续序列

128.最长连续序列
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

请你设计并实现时间复杂度为 O(n) 的算法解决此问题。

class Solution {
    public int longestConsecutive(int[] nums) {
        Arrays.sort(nums);
        Set<Integer> set = new HashSet<>();
        int maxlen = 0;
        int temp = 1;
        for(int num: nums){
            if(set.contains(num-1)&&!set.contains(num)){
                temp++;
            }else if(set.contains(num)){//去重
                temp=temp;
            }else{
                temp=1;
            }
            maxlen=Math.max(maxlen,temp);
            set.add(num);
        }
        return maxlen;
    }
}

区间

48.汇总区间

228.汇总区间
给定一个 无重复元素 的 有序 整数数组 nums 。

返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说,nums 的每个元素都恰好被某个区间范围所覆盖,并且不存在属于某个范围但不属于 nums 的数字 x 。

列表中的每个区间范围 [a,b] 应该按如下格式输出:

“a->b” ,如果 a != b
“a” ,如果 a == b

class Solution {
    public List<String> summaryRanges(int[] nums) {
        List<String> res = new ArrayList<>();
        
        int right=0;
        while(right<nums.length){
            int left=right;
            while(right<nums.length-1&&nums[right+1]==nums[right]+1){
                right++;
            }
            if(left!=right){
                res.add(nums[left]+"->"+nums[right]);
            }else{
                res.add(""+nums[left]);
            }
            right++;
        }
        return res;
    }
}

49.合井区间(hot100)

56.合井区间
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。

class Solution {
    public int[][] merge(int[][] intervals) {
        Arrays.sort(intervals, (intervals1, intervals2)->intervals1[0]-intervals2[0]);
        List<int[]> res = new ArrayList<>();
        int n = intervals.length;
        for(int i=0;i<n;i++){
            int L = intervals[i][0], R=intervals[i][1];
            //res.get(res.size()-1)是上一个区间
            if(res.size()==0||res.get(res.size()-1)[1]<L){
                res.add(new int[]{L, R});
            }else{
                res.get(res.size()-1)[1]=Math.max(res.get(res.size()-1)[1], R);
            }
        }
        return res.toArray(new int[res.size()][]);
    }
}

50.插入区间

57.插入区间
给你一个 无重叠的 ,按照区间起始端点排序的区间列表 intervals,其中 intervals[i] = [starti, endi] 表示第 i 个区间的开始和结束,并且 intervals 按照 starti 升序排列。同样给定一个区间 newInterval = [start, end] 表示另一个区间的开始和结束。

在 intervals 中插入区间 newInterval,使得 intervals 依然按照 starti 升序排列,且区间之间不重叠(如果有必要的话,可以合并区间)。

返回插入之后的 intervals。

注意 你不需要原地修改 intervals。你可以创建一个新数组然后返回它。

class Solution {
    public int[][] insert(int[][] intervals, int[] newInterval) {
        List<int[]> res = new ArrayList<>();
        int L = newInterval[0], R=newInterval[1];
        int n = intervals.length;

        boolean flag = false;
        for(int i=0;i<n;i++){
            int currL = intervals[i][0], currR = intervals[i][1];
            if(currL>R){
            //遇到第一个 满足currL>R的区间时,说明以后遍历到的区间不会与S重叠
                if(!flag){
                    res.add(new int[]{L,R});
                    flag=true;
                }
                res.add(new int[]{currL,currR});
            }else if(currR<L){
                res.add(new int[]{currL,currR});
            }else{
                L = Math.min(currL,L);
                R = Math.max(currR,R);
            }
        }
        if(!flag){
            res.add(new int[]{L,R});
        }
        return res.toArray(new int[res.size()][]);
    }
}

51.用最少数量的箭引爆气球

452.用最少数量的箭引爆气球
有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。

一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。

给你一个数组 points ,返回引爆所有气球所必须射出的 最小 弓箭数 。

class Solution {
    public int findMinArrowShots(int[][] points) {
        int res = 1;
        //Arrays.sort(points, (point1,point2)->(point1[1]-point2[1]));//int溢出
        Arrays.sort(points, new Comparator<int[]>() {
            public int compare(int[] point1, int[] point2){
                if(point1[1]>point2[1]){
                    return 1;
                }else if(point1[1]<point2[1]){
                    return -1;
                }else{
                    return 0;
                }
            }
        });
        int pos = points[0][1];//放在区间的最右边
        for(int i=0;i<points.length;i++){
            if(points[i][0]>pos){//没有相交的区间
                res++;
                pos = points[i][1];
            }
        }
        return res;        
    }
}

52.有效的括号

给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
每个右括号都有一个对应的相同类型的左括号。

class Solution {
    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();
        for(char c: s.toCharArray()){
            if(c=='('||c=='{'||c=='['){
                stack.push(c);
            }else if(c==')'){
                if(stack.isEmpty()||stack.peek()!='('){
                    return false;
                }else{
                    stack.pop();
                }
            }else if(c=='}'){
                if(stack.isEmpty()|| stack.peek()!='{'){
                    return false;
                }else{
                    stack.pop();
                }
            }else if(c==']'){
                if(stack.isEmpty()||stack.peek()!='['){
                    return false;
                }else{
                    stack.pop();
                }
            }
        }
        return stack.isEmpty();
    }
}

53.简化路径

71.简化路径
给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 ‘/’ 开头),请你将其转化为 更加简洁的规范路径。

在 Unix 风格的文件系统中规则如下:

一个点 ‘.’ 表示当前目录本身。
此外,两个点 ‘…’ 表示将目录切换到上一级(指向父目录)。
任意多个连续的斜杠(即,‘//’ 或 ‘///’)都被视为单个斜杠 ‘/’。
任何其他格式的点(例如,‘…’ 或 ‘…’)均被视为有效的文件/目录名称。
返回的 简化路径 必须遵循下述格式:

始终以斜杠 ‘/’ 开头。
两个目录名之间必须只有一个斜杠 ‘/’ 。
最后一个目录名(如果存在)不能 以 ‘/’ 结尾。
此外,路径仅包含从根目录到目标文件或目录的路径上的目录(即,不含 ‘.’ 或 ‘…’)。
返回简化后得到的 规范路径 。

class Solution {
    public String simplifyPath(String path) {
        Deque<String> stack = new ArrayDeque<>();
        String[] strs = path.split("/");
        for(String str: strs){
            if(str.equals("..")){
                if(!stack.isEmpty()) stack.pollLast();
            }else if(str.equals(".")||str.equals("")){
                continue;
            }else{
                stack.offerLast(str);
            }
        }
        StringBuilder res = new StringBuilder();
        if(stack.isEmpty()){
            res.append("/");
        }else{
            while(!stack.isEmpty()){
                res.append("/");
                res.append(stack.pollFirst());
            }
        }     
        return res.toString();
    }
}

54.最小栈

155.最小栈
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:

MinStack() 初始化堆栈对象。
void push(int val) 将元素val推入堆栈。
void pop() 删除堆栈顶部的元素。
int top() 获取堆栈顶部的元素。
int getMin() 获取堆栈中的最小元素。

class MinStack {
    Stack<Integer> stack;
    Stack<Integer> minstack;

    public MinStack() {
        stack = new Stack<>();
        minstack = new Stack<>();
    }
    
    public void push(int val) {
        stack.push(val);
        if(minstack.isEmpty()||minstack.peek()>=val){
            minstack.push(val);
        }
    }
    
    public void pop() {
        int num = stack.pop();
        if(num==minstack.peek()){
            minstack.pop();
        }
    }
    
    public int top() {
        return stack.peek();
    }
    
    public int getMin() {
        return minstack.peek();
    }
}

55.逆波兰表达式求值

150.逆波兰表达式求值
给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。

请你计算该表达式。返回一个表示表达式值的整数。

注意:

有效的算符为 ‘+’、‘-’、‘*’ 和 ‘/’ 。
每个操作数(运算对象)都可以是一个整数或者另一个表达式。
两个整数之间的除法总是 向零截断 。
表达式中不含除零运算。
输入是一个根据逆波兰表示法表示的算术表达式。
答案及所有中间计算结果可以用 32 位 整数表示。

输入:tokens = [“2”,“1”,“+”,“3”,“*”]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

class Solution {
    public int evalRPN(String[] tokens) {
        Stack<Integer>  stack = new Stack<>();  
        for(String str: tokens){
            if(str.equals("+")){
                int right = stack.pop();
                int left = stack.pop();
                stack.push(left+right);
            }else if(str.equals("-")){
                int right = stack.pop();
                int left = stack.pop();
                stack.push(left-right);
            }else if(str.equals("*")){
                int right = stack.pop();
                int left = stack.pop();
                stack.push(left*right);
            }else if(str.equals("/")){
                int right = stack.pop();
                int left = stack.pop();
                stack.push(left/right);
            }else{
                stack.push(Integer.parseInt(str));
            }
        }
        return stack.peek();
    }
}

56.基本计算器

224.基本计算器
给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。

注意:不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval() 。

输入:s = “1 + 1”
输出:2

class Solution {
    public int calculate(String s) {
        Stack<Integer> stack = new Stack<>();
        int res = 0;
        int flag = 1;//代表正负
        stack.push(1);
        for(int i=0;i<s.length();){
            char c = s.charAt(i);
            if(c=='('){
                stack.push(flag);
                i++;
            }else if(c==')'){
                stack.pop();
                i++;
            }else if(c=='+'){
                flag=stack.peek();
                i++;
            }else if(c=='-'){
                flag=-stack.peek();
                i++;
            }else if(c==' '){
                i++;
            }else{
                int total = 0;
                //多位数
                while(i<s.length()&&Character.isDigit(s.charAt(i))){
                    total = total*10+s.charAt(i)-'0';
                    i++;
                }
                res=res+flag*total;
            }
        }
        return res;
    }
}

链表

57.环形链表(hot100)

141.环形链表
给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head==null||head.next==null){
            return false;
        }
        ListNode slow = head;
        ListNode fast = head.next;
        while(fast!=null&&fast.next!=null){
            if(fast==slow){
                return true;
            }
            fast = fast.next.next;
            slow = slow.next;
        }
        return false;
    }
}

58.两数相加(hot100)

2.两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(0);
        ListNode res = dummy;
        int flag = 0;
        while(l1!=null||l2!=null||flag!=0){//三个条件
            int val1 = l1==null?0:l1.val;
            int val2 = l2==null?0:l2.val;
            int num = val1+val2+flag;
            flag = num/10;
            res.next = new ListNode(num%10);
            if(l1!=null){
                l1 = l1.next;
            }
            if(l2!=null){
                l2 = l2.next;
            }
            res = res.next;
        }
        return dummy.next;
    }
}

59.合并两个有序链表(hot100)

21.合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

/**
 * 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 mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode dummy = new ListNode(0);
        ListNode res = dummy;
        while(list1!=null&&list2!=null){
            if(list1.val<list2.val){
                res.next = list1;
                list1 = list1.next;
            }else{
                res.next = list2;
                list2 = list2.next;
            }
            res = res.next;
        }
        if(list1!=null){
            res.next = list1;
        }else{
            res.next = list2;
        }
        return dummy.next;
    }
}

60.随机链表的复制(hot100)

138.随机链表的复制
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。

构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。

例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。

返回复制链表的头节点。

用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:

val:一个表示 Node.val 的整数。
random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为 null 。
你的代码 只 接受原链表的头节点 head 作为传入参数。

class Solution {
    //递归
    HashMap<Node, Node> map = new HashMap<>();
    public Node copyRandomList(Node head) {
        if(head==null){
            return null;
        }
        if(!map.containsKey(head)){
            Node newNode = new Node(head.val);
            map.put(head, newNode);
            newNode.next = copyRandomList(head.next);
            newNode.random = copyRandomList(head.random);
        }
        return map.get(head);
    }
}

拆分

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/

class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) {
            return null;
        }
        //复制next节点,A→A′→B→B′→C→C′
        for(Node tmp = head; tmp!=null; tmp = tmp.next.next){
            Node newNode = new Node(tmp.val);
            newNode.next = tmp.next;
            tmp.next = newNode;
        }
        //复制random节点
        for(Node tmp = head; tmp!=null; tmp = tmp.next.next){
            //A′                                           A
            tmp.next.random = (tmp.random==null)?null:tmp.random.next;
        }
        //拆分
        Node res = head.next; 
        for(Node tmp = head; tmp!=null; tmp = tmp.next){
            Node newNode = tmp.next;
            tmp.next = tmp.next.next;
            newNode.next = (newNode.next==null)?null:newNode.next.next;
        }
        return res;
    }
}

61.反转链表 II

92.反转链表 II
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。

/**
 * 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 reverseBetween(ListNode head, int left, int right) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode leftLeftNode = dummy;
        for(int i=0;i<left-1;i++){
            leftLeftNode = leftLeftNode.next;
        }
        ListNode leftNode = leftLeftNode.next;
        
        ListNode rightNode = leftLeftNode;
        for(int i = left-1; i < right;i++){
            rightNode = rightNode.next;
        }
        ListNode rightRightNode = rightNode.next;
        //截取链表
        leftLeftNode.next = null;
        rightNode.next = null;
        ListNode[] headTail = reverseList(leftNode);
        leftLeftNode.next = headTail[0];
        headTail[1].next = rightRightNode;
        return dummy.next;
    }
    public ListNode[] reverseList(ListNode head) {
        ListNode[] headTail = new ListNode[2];
        headTail[1] = head;
        ListNode pre = null;
        while(head!=null){
            ListNode tmpNext = head.next;
            head.next = pre;
            pre = head;
            head = tmpNext;
        }
        headTail[0] = pre;
        return headTail;
    }
}

62.K个一组翻转链表

25.K个一组翻转链表
给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode dummy = new ListNode(0);
        ListNode curr = dummy;
        dummy.next = head;
        while(curr!=null){
            ListNode prepre = curr;
            ListNode pre = curr.next;
            for(int i=0;i<k;i++){
                curr = curr.next;
                if(curr==null){//不足一组直接返回
                    return dummy.next;
                }
            }
            ListNode tail = curr;
            ListNode tailTail = curr.next;
            ListNode[] list = reverse(pre, tail);
            prepre.next = list[0];
            list[1].next = tailTail;
            curr = list[1];
        }
        return dummy.next;
    }
    public ListNode[] reverse(ListNode head, ListNode tail){
        ListNode[] list = new ListNode[2];
        list[1] = head;
        list[0] = tail;
        ListNode pre = tail.next;//pre是下一组的开头
        while(pre!=tail){
            ListNode tmp = head.next;
            head.next = pre;
            pre = head;
            head = tmp;
        }
        return list;
    }
    
}

63.删除链表的倒数第 N个结点

19.删除链表的倒数第 N个结点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode fast = dummy;
        ListNode slow = dummy;
        for(int i=0;i<=n;i++){//fast多走n+1步
            fast = fast.next;
        }
        while(fast!=null){
            fast = fast.next;
            slow = slow.next;
        }
        slow.next = slow.next.next;//slow指向要删除的节点的前一个节点
        return dummy.next;
    }
}

64.删除排序链表中的重复元素 II

82.删除排序链表中的重复元素 II
给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。

输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode curr = dummy;
        while(curr.next!=null&&curr.next.next!=null){
            //curr是pre节点
            if(curr.next.val==curr.next.next.val){
                int val = curr.next.val;
                while(curr.next!=null&&curr.next.val==val){
                    curr.next = curr.next.next;//跳过一个节点
                }
            }else{
                curr = curr.next;
            }
        }
        return dummy.next;
    }
}

65.旋转链表

61.旋转链表
给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。

class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        if(head==null){
            return head;
        }
        ListNode curr = head;
        int len = 1;
        while(curr.next!=null){
            curr = curr.next;
            len++;
        }
        //找到尾结点,变成环
        curr.next = head;
        for(int i=0;i<len-k%len;i++){
            curr = curr.next;
        }
        //断开
        ListNode res = curr.next;
        curr.next = null;
        return res;
    }
}

66.分隔链表

86.分隔链表
给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。

你应当 保留 两个分区中每个节点的初始相对位置。

class Solution {
    public ListNode partition(ListNode head, int x) {
        ListNode small = new ListNode(0);
        ListNode large = new ListNode(0);
        ListNode smallHead = small;
        ListNode largeHead = large;
        while(head!=null){
            if(head.val<x){
                small.next = head;
                small = small.next;
            }else{
                large.next = head;
                large = large.next;
            }
            head = head.next; 
        }
        large.next = null;
        small.next = largeHead.next;
        return smallHead.next;
    }
}

67.LRU 缓存(hot100)

146.LRU 缓存
请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache 类:
LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。
函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。

class LRUCache {
    //双向链表
    class DlinkedNode{
        int key;
        int val;
        DlinkedNode pre;
        DlinkedNode next;
        public DlinkedNode() {}
        public DlinkedNode(int key, int val){
            this.val = val;
            this.key = key;
        }
    }
    //map:key,node; node:key,val;
    private HashMap<Integer, DlinkedNode> map = new HashMap<>();
    private int size;
    private int capacity;
    private DlinkedNode head,tail;//虚拟头尾节点

    public LRUCache(int capacity) {
        this.size = 0;
        this.capacity = capacity;
        head = new DlinkedNode();
        tail = new DlinkedNode();
        head.next = tail;
        tail.pre = head;
    }
    
    public int get(int key) {
        if(map.containsKey(key)){
            DlinkedNode node = map.get(key);
            moveToHead(node);
            return node.val;
        }else{
            return -1;
        }
    }
    
    public void put(int key, int value) {
        if(map.containsKey(key)){
            DlinkedNode node = map.get(key);
            node.val = value;
            moveToHead(node);
        }else{
            DlinkedNode node = new DlinkedNode(key, value);
            map.put(key, node);
            addToHead(node);
            size++;
            if(size>capacity){
                DlinkedNode tail = removeLast();
                map.remove(tail.key);
                size--;
            }
        }
        

    }
    public void moveToHead(DlinkedNode node){
        //删除节点
        node.pre.next = node.next;
        node.next.pre = node.pre;
        
        addToHead(node);
    }
    public void addToHead(DlinkedNode node){
        //node
        node.pre = head;
        node.next = head.next;
        //右边节点
        head.next.pre = node;
        //左边节点
        head.next = node;
    }
    public DlinkedNode removeLast(){
        DlinkedNode last = tail.pre;
        last.pre.next = tail;
        tail.pre = last.pre;
        return last;
    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */

二叉树

68.二叉树的最大深度(hot100)

104.二叉树的最大深度
给定一个二叉树 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 {
    public int maxDepth(TreeNode root) {
        if(root==null){
            return 0;
        }
        //递归
        int leftDepth = maxDepth(root.left);
        int rightDepth = maxDepth(root.right);
        return Math.max(leftDepth,rightDepth)+1;
    }
}

69.相同的树

100.相同的树
给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

/**
 * 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 true;
        }else if(p==null||q==null){
            return false;
        }else if(p.val!=q.val){
            return false;
        }else{//p.val==q.val
            boolean leftB = isSameTree(p.left, q.left);
            boolean rightB = isSameTree(p.right, q.right);
            return leftB&&rightB;
        }
    }
}

70.翻转二叉树(hot100)

226.翻转二叉树
给你一棵二叉树的根节点 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 {
    public TreeNode invertTree(TreeNode root) {
        if(root==null){
            return null;
        }
        //保留原始的子树,不要覆盖
        TreeNode leftTree = invertTree(root.left);
        TreeNode rightTree = invertTree(root.right);
        root.right = leftTree;
        root.left = rightTree;
        return root;
    }
}

71.对称二叉树(hot100)

101.对称二叉树
给你一个二叉树的根节点 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 {
    public boolean isSymmetric(TreeNode root) {
        return isSymmetricTwo(root.left, root.right);
    }
    public boolean isSymmetricTwo(TreeNode left, TreeNode right) {
        if(left==null&&right==null){
            return true;
        }else if(left==null||right==null){
            return false;
        }else if(left.val!=right.val){
            return false;
        }else{
            boolean leftB = isSymmetricTwo(left.right, right.left);
            boolean rightB = isSymmetricTwo(left.left, right.right);
            return leftB&&rightB;
        }
    }
}

72.从前序与中序遍历序列构造二叉树(hot100)

105.从前序与中序遍历序列构造二叉树
给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

/**
 * 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) {
        int n = preorder.length;
        TreeNode root = build(preorder,inorder,0,n-1,0,n-1);
        return root;
    }
    public TreeNode build(int[] preorder, int[] inorder, int preLeft, int preRight, int inLeft, int inRight) {
        if(preLeft>preRight||inLeft>inRight){
            return null;
        }
        TreeNode root = new TreeNode(preorder[preLeft]);
        int location = 0;//中序遍历的root位置
        for(int i=inLeft;i<=inRight;i++){
            if(inorder[i]==preorder[preLeft]){
                location = i;
                break;
            }
        }
        //前序遍历:root 左子树 右子树
        //中序遍历:左子树 root 右子树
        int leftlen = location-inLeft;//左子树中的节点数目
        root.left = build(preorder,inorder,preLeft+1,preLeft+leftlen,inLeft,location-1);
        root.right = build(preorder,inorder,preLeft+leftlen+1,preRight,location+1,inRight);
        return root;
    }
}

73.从中序与后序遍历序列构造二叉树

106.从中序与后序遍历序列构造二叉树
给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。

/**
 * 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) {
        //后序遍历:左子树 右子树 root
        //中序遍历:左子树 root 右子树
        int n = inorder.length;
        return build(inorder,postorder,0,n-1,0,n-1);
    }
    public TreeNode build(int[] inorder, int[] postorder, int inLeft, int inRight, int postLeft, int postRight) {
        if(inLeft>inRight){
            return null;
        }
        TreeNode root = new TreeNode(postorder[postRight]);
        int location = 0;
        for(int i=inRight;i>=inLeft;i--){
            if(inorder[i]==postorder[postRight]){
                location=i;
                break;
            }
        }
        int rightLen = inRight-location;
        root.left = build(inorder,postorder,inLeft,location-1,postLeft,postRight-rightLen-1);
        root.right = build(inorder,postorder,location+1,inRight,postRight-rightLen,postRight-1);
        return root;
    }
}

74.填充每个节点的下一个右侧节点指针II

117.填充每个节点的下一个右侧节点指针II
给定一个二叉树:

struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL 。

初始状态下,所有 next 指针都被设置为 NULL 。

你可能感兴趣的:(leetcode,面试,算法)