【leetcode】剑指Offer专项突击版含注释 Java版本(一)

目录

前言

以下文章主要是方便自我学习总结
感兴趣的也可一键三连互相学习

大部分算法题都是刷过很多次,知识点也比较重复,特殊情况下会重点讲解下代码思路

对应另一套算法题:

  1. 字节校园精选 66 道高频经典笔面试题(含多种思路)(上)
  2. 字节校园精选 66 道高频经典笔面试题(含多种思路)(下)

第一天(整数)

剑指 Offer II 002. 二进制加法(简单)

题目:剑指 Offer II 002. 二进制加法
此题和 67. 二进制求和 一模一样

给定两个 01 字符串 a 和 b ,请计算它们的和,并以二进制字符串的形式输出。

输入为 非空 字符串且只包含数字 1 和 0。

示例 1:

输入: a = “11”, b = “10”
输出: “101”

示例 2:

输入: a = “1010”, b = “1011”
输出: “10101”

提示:

每个字符串仅由字符 ‘0’ 或 ‘1’ 组成。
1 <= a.length, b.length <= 104
字符串如果不是 “0” ,就都不含前导零。


思路:

class Solution {
    public String addBinary(String a, String b) {

        int i = a.length() - 1;
        int j = b.length() - 1;
        int carry = 0;
        
        StringBuilder sb = new StringBuilder();
        while(i >= 0 || j >= 0 || carry != 0){
            int x = i >= 0 ? a.charAt(i) - '0' : 0;
            int y = j >= 0 ? b.charAt(j) - '0' : 0;
            int add = x + y + carry;
            // 添加对应的进位以及保留元素
            sb.append(add % 2);
            carry = add / 2;
            i--;
            j--;
        }

        return sb.reverse().toString();

    }
}

第二天(整数)

剑指 Offer II 004. 只出现一次的数字 (中等)

题目:剑指 Offer II 004. 只出现一次的数字

给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。

示例 1:

输入:nums = [2,2,3,2]
输出:3

示例 2:

输入:nums = [0,1,0,1,0,1,100]
输出:100

提示:

1 <= nums.length <= 3 * 104
-231 <= nums[i] <= 231 - 1
nums 中,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次

进阶:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

注意:本题与主站 137 题相同:137. 只出现一次的数字 II


class Solution {
    public int singleNumber(int[] nums) {

        // 此ans为累加的值
        int ans = 0;
        // 遍历32个 位
        for(int i = 0;i < 32;i++){
            int total = 0;
            // 遍历数组中每个位的运算,如果这个位是3的倍数则一定不是答案
            for(int num : nums){
                // num从右往左,而且是和1相与查看
                total += ((num >> i) & 1);
            }
            if(total % 3 != 0){
                // 对应将其所有的ans进行或,也就是合并。
                ans |= (1 << i);
            }

        }
        return ans;
    }
}

剑指 Offer II 005. 单词长度的最大乘积(中等)

题目:剑指 Offer II 005. 单词长度的最大乘积

给定一个字符串数组 words,请计算当两个字符串 words[i] 和 words[j] 不包含相同字符时,它们长度的乘积的最大值。假设字符串中只包含英语的小写字母。如果没有不包含相同字符的一对字符串,返回 0。

示例 1:

输入: words = [“abcw”,“baz”,“foo”,“bar”,“fxyz”,“abcdef”]
输出: 16
解释: 这两个单词为 “abcw”, “fxyz”。它们不包含相同字符,且长度的乘积最大。

示例 2:

输入: words = [“a”,“ab”,“abc”,“d”,“cd”,“bcd”,“abcd”]
输出: 4
解释: 这两个单词为 “ab”, “cd”。

示例 3:

输入: words = [“a”,“aa”,“aaa”,“aaaa”]
输出: 0
解释: 不存在这样的两个单词。

提示:

2 <= words.length <= 1000
1 <= words[i].length <= 1000
words[i] 仅包含小写字母

注意:本题与主站 318 题相同:318. 最大单词长度乘积


利用位运算的存储

class Solution {
    public int maxProduct(String[] words) {
        // 定义一个marks来存储 每个单词的 位
        int[] marks = new int[words.length];
        for(int i = 0;i < words.length;i++){
            String word = words[i];

            // 将其每个位都存储起来,通过1左移多少位,并且 或
            for(int j = 0;j < word.length();j++){
                marks[i] |= 1 << (word.charAt(j) - 'a');
            }
        }

        int ans = 0;
        for(int i = 0;i < marks.length;i++){
            for(int j = i + 1;j < marks.length;j++){
                // 如果两者为0,说明不含相同字母,将其words数组的length相乘
                if((marks[i] & marks[j]) == 0){
                    ans = Math.max(ans,words[i].length() * words[j].length());
                }
            }
        }
        return ans;
    }
}

剑指 Offer II 006. 排序数组中两个数字之和(简单)

题目:剑指 Offer II 006. 排序数组中两个数字之和

给定一个已按照 升序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。

函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 0 开始计数 ,所以答案数组应当满足 0 <= answer[0] < answer[1] < numbers.length 。

假设数组中存在且只存在一对符合条件的数字,同时一个数字不能使用两次。

示例 1:

输入:numbers = [1,2,4,6,10], target = 8
输出:[1,3]
解释:2 与 6 之和等于目标数 8 。因此 index1 = 1, index2 = 3 。

示例 2:

输入:numbers = [2,3,4], target = 6
输出:[0,2]

示例 3:

输入:numbers = [-1,0], target = -1
输出:[0,1]

提示:

2 <= numbers.length <= 3 * 104
-1000 <= numbers[i] <= 1000
numbers 按 递增顺序 排列
-1000 <= target <= 1000
仅存在一个有效答案

注意:本题与主站 167 题相似(下标起点不同):167. 两数之和 II - 输入有序数组


本身已经排序好,可以使用二分查找

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int left = 0;
        int right = numbers.length - 1;

        // 此处定义的sum总值,需为numbers[left] + numbers[right];
        while(left <= right){
            int sum = numbers[left] + numbers[right];
            if(sum == target){
                return new int[]{left,right};
            }else if (sum < target) {
                left++;
            }else if(sum > target){
                right--;               
            }
        }
        // 如果值不符,则返回-1,-1
        return new int[]{-1,-1};
    }
}

第三天(数组)

剑指 Offer II 007. 数组中和为 0 的三个数(中等)

题目:剑指 Offer II 007. 数组中和为 0 的三个数

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

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

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例 2:

输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。

示例 3

输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

提示:

3 <= nums.length <= 3000
-105 <= nums[i] <= 105

注意:本题与主站 15 题相同:15. 三数之和


class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        int n = nums.length;
        List<List<Integer>> list = new ArrayList<List<Integer>>();
        for(int i = 0;i < n - 2;i++){

            // 大于0,则后面的数字也是大于零(排序后是递增的)
            if(nums[i] > 0)break;
            // 代表第一个值重复了,去重,此处的去重是 不同子list的开头
            if(i > 0 && nums[i] == nums[i - 1])continue;
            System.out.println(i);
            int left = i + 1;
            int right = n - 1;
            while(left < right){
                int sum = nums[left] + nums[right] + nums[i];
                if(sum == 0){
                    List<Integer> sonlist = new ArrayList<>();
                    sonlist.add(nums[left]);
                    sonlist.add(nums[right]);
                    sonlist.add(nums[i]);
                    list.add(sonlist);
                    //list.add(new ArrayList<>(Arrays.asList(nums[i],nums[left],nums[right])));
                    //左指针前进并去重
                    while(left < right && nums[left] == nums[++left]);
                    //右指针前进并去重
                    while(left < right && nums[right] == nums[--right]);

                }else if(sum < 0){
                    //左指针前进并去重
                    while (left < right && nums[left] == nums[++left]);
                }else if(sum > 0){
                    //右指针前进并去重
                    while (left < right && nums[right] == nums[--right]);
                }

            }
        }
        return list;
    }

}

剑指 Offer II 008. 和大于等于 target 的最短子数组(中等)***

题目:剑指 Offer II 008. 和大于等于 target 的最短子数组

给定一个含有 n 个正整数的数组和一个正整数 target 。

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

示例 1:

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

示例 2:

输入:target = 4, nums = [1,4,4]
输出:1

示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

提示:

1 <= target <= 109
1 <= nums.length <= 105
1 <= nums[i] <= 105

进阶:

如果你已经实现 O(n) 时间复杂度的解法, 请尝试设计一个 O(n log(n)) 时间复杂度的解法。

注意:本题与主站 209 题相同:209. 长度最小的子数组


class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int start = 0;
        int end = 0;
        int n = nums.length;

        int sum = 0;

        // 设置一个最大值
        int ans = Integer.MAX_VALUE;
        while(end < n){
            // 对应将其end的值都加入
            sum += nums[end];
            // 如果大于等于target 则进行筛选ans的下标差
            while(sum >= target){
                ans = Math.min(ans,end - start + 1);

                // 不要忘记将其nums【start】 减去,因为是滑动串窗口
                sum -= nums[start];
                start++;
            }
            end++;
        }

        // 判定最后的值是否符合
        return ans == Integer.MAX_VALUE ? 0 : ans;

    }
}

剑指 Offer II 009. 乘积小于 K 的子数组(中等)

题目:剑指 Offer II 009. 乘积小于 K 的子数组

给定一个正整数数组 nums和整数 k ,请找出该数组内乘积小于 k 的连续的子数组的个数。

示例 1:

输入: nums = [10,5,2,6], k = 100
输出: 8
解释: 8 个乘积小于 100 的子数组分别为: [10], [5], [2], [6], [10,5], [5,2], [2,6], [5,2,6]。
需要注意的是 [10,5,2] 并不是乘积小于100的子数组。

示例 2:

输入: nums = [1,2,3], k = 0
输出: 0

提示:

1 <= nums.length <= 3 * 104
1 <= nums[i] <= 1000
0 <= k <= 106

注意:本题与主站 713 题相同:713. 乘积小于 K 的子数组


每次加上以当前数字为结尾的所有子数组数量

/**
        如果值为10 5 2 6
        第一个窗口为10,整体答案为【10】共有1个
        第二个窗口为10 5 ,整体答案为【10,5】【5】共有2个
        第三个窗口为10,5,2,因为不满足,所以把10排出去,窗口为5,2.整体答案为【5,2】,【2】共有2个
        第四个窗口为5 2 6,整体答案为【5,2,6】【2,6】【6】共有三个

*/
class Solution {
    public int numSubarrayProductLessThanK(int[] nums, int k) {

        int left = 0;
        int right = 0;
        int n = nums.length;

        int sum = 1;
        int ans = 0;

        while(right < n){
            sum *= nums[right];
            // 注意边界条件,不要忘记left <= right
            while(sum >= k && left <= right){
                sum /= nums[left];
                left++;
            }
            
            // 每次加上以当前数字为结尾的所有子数组数量。
            ans += right - left + 1;
            right++;
            
        }
        return ans;

    }
}

第四天(数组)滑动窗口

剑指 Offer II 010. 和为 k 的子数组(中等)

题目:剑指 Offer II 010. 和为 k 的子数组
此题和 560. 和为 K 的子数组题目一样

给定一个整数数组和一个整数 k ,请找到该数组中和为 k 的连续子数组的个数。

示例 1:

输入:nums = [1,1,1], k = 2
输出: 2
解释: 此题 [1,1] 与 [1,1] 为两种不同的情况

示例 2:

输入:nums = [1,2,3], k = 3
输出: 2

提示:

1 <= nums.length <= 2 * 104
-1000 <= nums[i] <= 1000
-107 <= k <= 107


思路:

class Solution {
    public int subarraySum(int[] nums, int k) {
        int n = nums.length;
        Map<Integer,Integer> map = new HashMap<>();
        map.put(0,1);

        int pre = 0;
        int count = 0;
        for(int i = 0;i < n;i++){
            // 前缀和
            pre += nums[i];
            if(map.containsKey(pre - k)){
                count += map.get(pre - k);
            }
            // 为下一个临界值 增加一个1
            map.put(pre,map.getOrDefault(pre,0) + 1);
        }
        return count;
    }
}

剑指 Offer II 012. 左右两边子数组的和相等(简单)

题目:剑指 Offer II 012. 左右两边子数组的和相等
此题和724. 寻找数组的中心下标 题目一样

给你一个整数数组 nums ,请计算数组的 中心下标 。

数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。

如果中心下标位于数组最左端,那么左侧数之和视为 0 ,因为在下标的左侧不存在元素。这一点对于中心下标位于数组最右端同样适用。

如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回 -1 。

示例 1:

输入:nums = [1,7,3,6,5,6]
输出:3
解释:
中心下标是 3 。
左侧数之和 sum = nums[0] + nums[1] + nums[2] = 1 + 7 + 3 = 11 ,
右侧数之和 sum = nums[4] + nums[5] = 5 + 6 = 11 ,二者相等。

示例 2:

输入:nums = [1, 2, 3]
输出:-1
解释:
数组中不存在满足此条件的中心下标。

示例 3:

输入:nums = [2, 1, -1]
输出:0
解释:
中心下标是 0 。
左侧数之和 sum = 0 ,(下标 0 左侧不存在元素),
右侧数之和 sum = nums[1] + nums[2] = 1 + -1 = 0 。

提示:

1 <= nums.length <= 104
-1000 <= nums[i] <= 1000


思路:

class Solution {
    public int pivotIndex(int[] nums) {
        // 通过java8的特性,使用stream函数,计算总和
        int total = Arrays.stream(nums).sum();
        int n = nums.length;
        int sum = 0;

        // 计算前缀和,对应是2倍的sum + nums 等于total则返回i
        for(int i = 0;i < n;i++){
            if(2 * sum + nums[i] == total){
                return i;
            }
            sum += nums[i];
        }
        return -1;

    }
}

另外一种思路是,计算前缀和都存储在数组中,计算后缀和也存储在数组中,比较两个数组是否有相等的值,返回对应的i即可

第五天(字符串)

剑指 Offer II 016. 不含重复字符的最长子字符串(中等)

题目:剑指 Offer II 016. 不含重复字符的最长子字符串

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

示例 1:

输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子字符串是 “abc”,所以其长度为 3。

示例 2:

输入: s = “bbbbb”
输出: 1
解释: 因为无重复字符的最长子字符串是 “b”,所以其长度为 1。

示例 3:

输入: s = “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。

示例 4:

输入: s = “”
输出: 0

提示:

0 <= s.length <= 5 * 104
s 由英文字母、数字、符号和空格组成

注意:本题与主站 3 题相同: 3. 无重复字符的最长子串

class Solution {
    public int lengthOfLongestSubstring(String s) {

        // 哈希集合,记录每个字符是否出现过
        Set<Character> set = new HashSet<Character>();
        int n = s.length();

        // 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
        int rk = -1, ans = 0;
        for (int i = 0; i < n; ++i) {

            if (i != 0) {
                // 左指针向右移动一格,移除一个字符
                set.remove(s.charAt(i - 1));
            }

            while (rk + 1 < n && !set.contains(s.charAt(rk + 1))) {
                // 不断地移动右指针
                set.add(s.charAt(rk + 1));
                ++rk;
            }
            
            // 第 i 到 rk 个字符是一个极长的无重复字符子串
            ans = Math.max(ans, rk - i + 1);
        }
        return ans;
    }
}

第六天(字符串)

剑指 Offer II 018. 有效的回文(简单)

题目:剑指 Offer II 018. 有效的回文

给定一个字符串 s ,验证 s 是否是 回文串 ,只考虑字母和数字字符,可以忽略字母的大小写。

本题中,将空字符串定义为有效的 回文串 。

示例 1:

输入: s = “A man, a plan, a canal: Panama”
输出: true
解释:“amanaplanacanalpanama” 是回文串

示例 2:

输入: s = “race a car”
输出: false
解释:“raceacar” 不是回文串

提示:

1 <= s.length <= 2 * 105
字符串 s 由 ASCII 字符组成

注意:本题与主站 125 题相同:125. 验证回文串


在原字符串进行操作,是最优d

class Solution {
    public boolean isPalindrome(String s) {
        // 将其拆分成单个字符
        char[] c = s.toLowerCase().toCharArray();

        // 对应都变为边界值
        int left = 0, right = c.length - 1;

        while(left < right){
            // 直到遇到左边第一个单词
            while(!isValid(c[left]) && left < right){
                ++left;
            }


            // 直到遇到右边第一个单词
            while(!isValid(c[right]) && left < right){
                --right;
            }
            // 判断两者是否相等
            if(c[left] !=  c[right]){
                return false;
            }
            ++left;
            --right;
        }
        return true;
    }

    private boolean isValid(char c){
        return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
    }
}

剑指 Offer II 019. 最多删除一个字符得到回文(简单)

题目:剑指 Offer II 019. 最多删除一个字符得到回文

给定一个非空字符串 s,请判断如果 最多 从字符串中删除一个字符能否得到一个回文字符串。

示例 1:

输入: s = “aba”
输出: true

示例 2:

输入: s = “abca”
输出: true
解释: 可以删除 “c” 字符 或者 “b” 字符

示例 3:

输入: s = “abc”
输出: false

提示:

1 <= s.length <= 105
s 由小写英文字母组成

注意:本题与主站 680 题相同:680. 验证回文串 II


class Solution {
    public boolean validPalindrome(String s) {
        int n = s.length();

        int left = 0;
        int right = n - 1;
        // 前后进行判断
        while(left < right){
            char c1 = s.charAt(left);
            char c2 = s.charAt(right);
            // 如果两者的值相等,则 前进以及后退
            if(c1 == c2){
                left++;
                right--;

            }else {
                return validPalindrome(s,left,right - 1) || validPalindrome(s,left + 1,right);
            }
        }
        return true;
    }

    public boolean validPalindrome(String s, int left, int right){
        for(int i = left,j = right ; i < j ; i++,j--){
            if(s.charAt(i) != s.charAt(j)){
                return false;
            }
        }

        return true;
    }
}

剑指 Offer II 020. 回文子字符串的个数(中等)*

题目:剑指 Offer II 020. 回文子字符串的个数

给定一个字符串 s ,请计算这个字符串中有多少个回文子字符串。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

示例 1:

输入:s = “abc”
输出:3
解释:三个回文子串: “a”, “b”, “c”

示例 2:

输入:s = “aaa”
输出:6
解释:6个回文子串: “a”, “a”, “a”, “aa”, “aa”, “aaa”

提示:

1 <= s.length <= 1000
s 由小写英文字母组成

注意:本题与主站 647 题相同:647. 回文子串


class Solution {
    public int countSubstrings(String s) {
        int n = s.length();
        int sum = 0;
        
        for(int i = 0;i <n;i++){
            // 边界处理问题,使用奇数 偶数处理
            for(int j = 0;j <= 1;j++){

                // 定义left 以及 right的边界值
                int left = i;
                int right = i +j;
                // 此处使用while来判断两者是否相等
                while(left >= 0 && right < n && s.charAt(left--) == s.charAt(right++)){
                    sum++;
                }
            }
        }
        return sum;
    }
}

第七天(链表)

剑指 Offer II 021. 删除链表的倒数第 n 个结点(中等)

题目:剑指 Offer II 021. 删除链表的倒数第 n 个结点
此题和19. 删除链表的倒数第 N 个结点一样

注意一开始定义的细节

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0,head);

        // 此处的first 为head节点,second为dummy节点
        // 注意其中的区别
        ListNode first = head;
        ListNode second = dummy;

        for(int i = 0;i < n;i++){
            first = first.next;
        }

        while(first != null){
            first = first.next;
            second = second.next;
        }
        second.next = second.next.next;
        return dummy.next;
    }
}

剑指 Offer II 022. 链表中环的入口节点(中等)

题目:剑指 Offer II 022. 链表中环的入口节点
142. 环形链表 II一模一样

注意一开始循环的细节

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;

        // 此处的条件不是fast != slow,因为一开始就是相等的
        while(true){
            // 中间为null的时候返回为null
            if(fast == null || fast.next == null) return null;
            slow = slow.next;
            fast = fast.next.next;
            // 相等的时候 break出来
            if (fast == slow) break;
        }

        fast = head;
        while(fast != slow){
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }
}

剑指 Offer II 023. 两个链表的第一个重合节点(简单)

题目:剑指 Offer II 023. 两个链表的第一个重合节点
和题目:160. 相交链表一模一样

内部的if else 要分开

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA == null || headB == null){
            return null;
        }

        ListNode a = headA;
        ListNode b = headB;
        //判断的初始条件
        while(a != b){

            //两者的判断条件一定要分开
            if(a != null){
                a = a.next;
            }else{
                a = headB;
            }

            if(b != null){
                b = b.next;
            }else{
                b = headA;
            }
        }
        return a;

        
    }
}

第八天(链表)

剑指 Offer II 024. 反转链表(简单)

题目:剑指 Offer II 024. 反转链表
相似题目:206. 反转链表

class Solution {
    public ListNode reverseList(ListNode head) {
        
        // 到达最后一个节点的时候返回head
        if(head == null || head.next == null) {
            return head;
        }else {
            ListNode newhead = reverseList(head.next);
            head.next.next = head;
            // 递归的时候 如果不为null 会变成一个环
            head.next = null;
            return newhead;
        }

    }
}

迭代的用法:
此处迭代的用法 不用创建虚拟头结点

class Solution {
    public ListNode reverseList(ListNode head) {
        // 反转链表 ,prev 与 cur的值
        ListNode prev = null;
        ListNode cur = head;
        while(cur != null){
            // next值 
            ListNode node = cur.next;
            cur.next = prev;

            // 相互挪位置
            prev = cur;
            cur = node;
        }
        return prev;
    }
}

剑指 Offer II 025. 链表中的两数相加(中等)

题目:剑指 Offer II 025. 链表中的两数相加
相似题目:445. 两数相加 II

倒序只能用栈,而且用的头插法,不用再reverse节点

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        // 将两个链表的所有值都存储在栈中
        // 而且比较的是进位每个数值,所以直接使用Integer
        Deque<Integer> stack1 = new LinkedList<>();
        Deque<Integer> stack2 = new LinkedList<>();
        while(l1 != null){
            stack1.push(l1.val);
            l1 = l1.next;
        }
        while(l2 != null){
            stack2.push(l2.val);
            l2 = l2.next;
        }

        int carry = 0;
        ListNode ans = null;
        // 总体的条件不要忘记进位,最后一位的进位
        // !stack1.isEmpty() 堆栈为空的判断形式
        while(!stack1.isEmpty() || !stack2.isEmpty() || carry != 0){
            int a = stack1.isEmpty() ? 0:stack1.pop();
            int b = stack2.isEmpty() ? 0:stack2.pop();
            int cur = a + b + carry;
            // 方便之后进位
            carry = cur / 10;
            // 没有进位的位数存储起来,通过ListNode进行创建
            cur %= 10;
            
            ListNode node = new ListNode(cur);
            // 移动位置 而且这是头插法 需要注意下
            node.next = ans;
            ans = node;
        }
        return ans;

    }
}

剑指 Offer II 026. 重排链表(中等)

题目:剑指 Offer II 026. 重排链表
相似题目:143. 重排链表

使用list进行重排

class Solution {
    public void reorderList(ListNode head) {
        List<ListNode>list = new ArrayList<>();
        ListNode cur = head;
        while(cur != null){
            list.add(cur);
            cur = cur.next;
        }

        // 定义全局的变量
        int i = 0;
        // 注意下标是0到3
        int j = list.size()-1;
        while(i < j){
            list.get(i).next = list.get(j);
            i++;
            list.get(j).next = list.get(i);
            j--;
        }
        list.get(i).next=null;
    }
}

另一种思路就是:
取中间节点(快慢指针),反转后缀,对其两者进行合并
中间有个节点是while(fast.next != null && fast.next.next != null ){
找中间节点,判定条件,如果是找有无环,本身if(fast == null || fast.next == null) return null;即可

class Solution {
    public void reorderList(ListNode head) {

        if (head == null) {
            return;
        }

        ListNode mid = middlenode(head);
        ListNode l1 = head;

        // 别忘记这个节点 比如 1234 ,中点是2,则34反转
        ListNode l2 = mid.next;
        // 并且将其2的next置为空
        mid.next = null;

        l2 = reverse(l2);
        merge(l1, l2);

    }

    // 快慢指针查找链表的终点
    public ListNode middlenode(ListNode head){
        ListNode slow = head;
        ListNode fast = head;
        while(fast.next != null && fast.next.next != null ){
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }

    // 反转链表
    public ListNode reverse(ListNode head){
        ListNode prev = null;
        ListNode cur = head;
        while(cur != null ){
            ListNode node = cur.next;
            cur.next = prev;
            prev = cur;
            cur = node;
        }
        return prev;
    }

    // 合并链表节点
    public void merge(ListNode l1,ListNode l2){
        while(l1 != null && l2 != null){
            // 记住下一个节点 方便遍历
            ListNode cur1 = l1.next;
            l1.next = l2;
            
            // 同样记住下一个节点,方便遍历
            ListNode cur2 = l2.next;
            l2.next = cur1;
            
            // 回到初始状态,方便下一次的迭代
            l1 = cur1;
            l2 = cur2;
        }
    }
}

第九天(链表)

剑指 Offer II 027. 回文链表(简单)

题目:剑指 Offer II 027. 回文链表
相似题目:234. 回文链表


回文链表的处理方式有很多种,此处讲解下最优的方式:(这题和上面的重排链表思路一致)

class Solution {
    public boolean isPalindrome(ListNode head) {
        if (head == null) {
            return false;
        }

        // 找到中间节点
        ListNode mid = middlenode(head);
        ListNode l1 = head;

        // 别忘记这个节点 比如 1234 ,中点是2,则34反转,3才是开始的节点
        ListNode l2 = mid.next;
        // 并且将其2的next置为空
        mid.next = null;
        l2 = reverse(l2);

        // 此处进行比较
        while(l1 != null && l2 != null){
            if(l1.val != l2.val){
                return false;
            }
            l1 = l1.next;
            l2 = l2.next;
        }
        return true;

    }

    // 快慢指针查找链表的 中点前一个
    public ListNode middlenode(ListNode head){
        ListNode slow = head;
        ListNode fast = head;
        while(fast.next != null && fast.next.next != null ){
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }

    // 反转链表
    public ListNode reverse(ListNode head){
        ListNode prev = null;
        ListNode cur = head;
        while(cur != null ){
            ListNode node = cur.next;
            cur.next = prev;
            prev = cur;
            cur = node;
        }
        return prev;
    }

}

第十二天(栈)

剑指 Offer II 036. 后缀表达式(中等)

题目:剑指 Offer II 036. 后缀表达式
150. 逆波兰表达式求值一模一样

题目:(省略)

class Solution {
    public int evalRPN(String[] tokens) {
        Deque<Integer> stack = new LinkedList<>();
        int n = tokens.length;
        for(int i = 0 ;i < n;i++){
            if(isNumber(tokens[i])){
                // 将其转换为数字,再放到stack中
                stack.push(Integer.parseInt(tokens[i]));
            }else{
                // 此处已经转换为了数字,所以pop就可
                int num2 = stack.pop();
                int num1 = stack.pop();
                switch(tokens[i]){
                    case "+":
                        stack.push(num1 + num2);
                        break;
                    case "-":
                        stack.push(num1 - num2);
                        break;
                    case "*":
                        stack.push(num1 * num2);
                        break;
                    case "/":
                        stack.push(num1 / num2);
                        break;
                }
            }
        }

        return stack.pop();
    }

    public boolean isNumber(String token){
        return !("+".equals(token) || ("-".equals(token) || ("*".equals(token) || ("/").equals(token))));
    }
}

第十四天(队列)

剑指 Offer II 041. 滑动窗口的平均值(简单)

题目:剑指 Offer II 041. 滑动窗口的平均值
346.数据流中的移动平均值一模一样

题目:

给定一个整数数据流和一个窗口大小,根据该滑动窗口的大小,计算滑动窗口里所有数字的平均值。

实现 MovingAverage 类:

MovingAverage(int size) 用窗口大小 size 初始化对象。
double next(int val) 成员函数 next 每次调用的时候都会往滑动窗口增加一个整数,请计算并返回数据流中最后 size 个值的移动平均值,即滑动窗口里所有数字的平均值。

示例:

输入:
inputs = [“MovingAverage”, “next”, “next”, “next”, “next”]
inputs = [[3], [1], [10], [3], [5]]
输出:
[null, 1.0, 5.5, 4.66667, 6.0]
解释:
MovingAverage movingAverage = new MovingAverage(3);
movingAverage.next(1); // 返回 1.0 = 1 / 1
movingAverage.next(10); // 返回 5.5 = (1 + 10) / 2
movingAverage.next(3); // 返回 4.66667 = (1 + 10 + 3) / 3
movingAverage.next(5); // 返回 6.0 = (10 + 3 + 5) / 3

提示:

1 <= size <= 1000
-105 <= val <= 105
最多调用 next 方法 104

class MovingAverage {
    Queue<Integer> queue;
    int size;
    // 定义为double类型
    double sum;
    /** Initialize your data structure here. */
    public MovingAverage(int size) {
        queue = new ArrayDeque<>();
        // 定义size大小
        this.size = size;
        sum = 0;
    }
    
    public double next(int val) {
        // 如果size提前为size,则将队头出队,并让sum减去
        if(queue.size() == size){
            sum -= queue.poll();
        }
        queue.offer(val);
        sum += val;

        // 此处的分母是队列的尺寸,而不是size
        return sum / queue.size();
    }
}

剑指 Offer II 042. 最近请求次数(简单)

题目:剑指 Offer II 042. 最近请求次数
此题和933. 最近的请求次数一模一样

题目:

写一个 RecentCounter 类来计算特定时间范围内最近的请求。

请实现 RecentCounter 类:

RecentCounter() 初始化计数器,请求数为 0 。
int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。
保证 每次对 ping 的调用都使用比之前更大的 t 值。

示例:

输入
inputs = [“RecentCounter”, “ping”, “ping”, “ping”, “ping”]
inputs = [[], [1], [100], [3001], [3002]]
输出:
[null, 1, 2, 3, 3]
解释:
RecentCounter recentCounter = new RecentCounter();
recentCounter.ping(1); // requests = [1],范围是 [-2999,1],返回 1
recentCounter.ping(100); // requests = [1, 100],范围是 [-2900,100],返回 2
recentCounter.ping(3001); // requests = [1, 100, 3001],范围是 [1,3001],返回 3
recentCounter.ping(3002); // requests = [1, 100, 3001, 3002],范围是 [2,3002],返回 3

提示:

1 <= t <= 109
保证每次对 ping 调用所使用的 t 值都 严格递增
至多调用 ping 方法 104


通过定义一个队列,将其队列头与其t-3000进行判定

class RecentCounter {
    Queue<Integer> queue;
    public RecentCounter() {
        queue = new ArrayDeque<>();
    }
    
    public int ping(int t) {
        queue.offer(t);
        while(queue.peek() < t - 3000){
            queue.poll();
        }
        return queue.size();
    }
}

第十五天(队列)

剑指Offer II 044. 二叉树每层的最大值(中等)

题目:剑指 Offer II 044. 二叉树每层的最大值
相似题目:515. 在每个树行中找最大值

class Solution {
    public List<Integer> largestValues(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        if(root == null) return list;

        Queue<TreeNode> que = new LinkedList<>();
        que.offer(root);
        while(!que.isEmpty()){
            int n = que.size();
            // 每一层都初始化Max节点
            int Max = Integer.MIN_VALUE;
            for(int i = 0;i < n;i++){
                TreeNode node = que.poll();
                // for遍历筛选最大的max
                Max = Math.max(Max,node.val);
                if(node.left != null)que.offer(node.left);
                if(node.right != null)que.offer(node.right);
            }
            list.add(Max);
        }

        return list;
    }
}

剑指 Offer II 045. 二叉树最底层最左边的值(中等)

题目:剑指 Offer II 045. 二叉树最底层最左边的值
相似的题目:513. 找树左下角的值

大致的思想通过层次遍历,找最左下角的节点

class Solution {
    public int findBottomLeftValue(TreeNode root) {
        if(root == null)return 0;
		
		// 定义一个全局变量ret,为了输出返回
        int ret = 0;
        Queue<TreeNode> que = new LinkedList<>();
        que.offer(root);
        while(!que.isEmpty()){
            int n = que.size();   
            for(int i = 0;i < n;i++){
                TreeNode node = que.poll();
                // i为0的时候,将其存储,每一层都会将其覆盖
                if(i == 0)ret = node.val;
                if(node.left != null) que.offer(node.left);
                if(node.right != null)que.offer(node.right);
                
            }
        }
        // 此为最后一层的ret值
        return ret;

    }
}

剑指 Offer II 046. 二叉树的右侧视图(中等)

题目:剑指 Offer II 046. 二叉树的右侧视图
相似的题目:199. 二叉树的右视图

class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        if(root == null)return list;

        Queue<TreeNode> que = new LinkedList<>();
        que.offer(root);
        while(!que.isEmpty()){
            int n = que.size();
            for(int i = 0;i < n; i++){
                TreeNode node = que.poll();
                // 只保存最后一个节点,每一层都是,所以使用list列表添加
                if(i == n-1)list.add(node.val);
                if(node.left != null)que.offer(node.left);
                if(node.right != null)que.offer(node.right);
            }
        }
        return list;

    }
}
  • 27
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
题目描述: 给定一个由 n 个节点组成的树,每个节点都有一个权值。定义一个节点的权值为其子树中所有节点的权值之和。请你返回所有满足下列条件的节点的权值之和: 该节点位于树的重心以上,即如果将该节点删除后,剩余各个连通分量的节点权值最大值最小。 如果节点数为偶数,则要求上述节点在剩余节点中,左右两部分节点数目相同;如果节点数为奇数,则要求左部分节点数目比右部分节点数目多一。 示例 1: 输入:edges = [[1,2],[1,3],[1,4],[4,5]], weight = [2,3,1,4] 输出:15 解释:树上的节点权值为 [,2,3,1,4] 。重心是节点 1 ,删除后为两个子树 [2,3,4] 和 [5] 。剩余节点权值分别为 9 和 4,均最小化。 示例 2: 输入:edges = [[1,2],[2,3],[3,4],[4,5],[5,6],[6,7],[7,8],[8,9]], weight = [1,2,3,4,5,6,7,8,9] 输出:31 解释:树上的节点权值为 [,1,2,3,4,5,6,7,8,9] 。重心是节点 5 ,删除后为两个子树 [1,2,3,4,6,7,8,9] 和 [] 。剩余节点权值分别为 33 和 ,均最小化。 提示: 1 <= n <= 10^5 edges.length == n - 1 edges[i].length == 2 1 <= edges[i][], edges[i][1] <= n 1 <= weight.length <= n 1 <= weight[i] <= 10^5 解题思路: 题目要求我们找到树的重心,然后删除重心以上的节点,使得剩余各个连通分量的节点权值最大值最小。 首先,我们需要知道什么是树的重心。树的重心是树上的一个节点,如果将该节点删除后,剩余各个连通分量的节点数最大值最小,那么这个节点就是树的重心。 我们可以使用两次 DFS 来解决这个问题。第一次 DFS 用来求出树的重心,第二次 DFS 用来计算删除重心以上的节点后,剩余各个连通分量的节点权值之和。 具体来说,我们可以先从任意一个节点开始,进行一次 DFS,求出以该节点为根的子树中的节点数和子树中所有节点的权值之和。然后,我们可以再进行一次 DFS,求出以该节点为根的子树中,删除该节点后,剩余各个连通分量的节点数最大值。我们可以使用一个数组 subsize 来记录每个节点的子树大小,使用一个数组 sum 来记录每个节点的子树中所有节点的权值之和。我们可以使用一个变量 ans 来记录删除重心以上的节点后,剩余各个连通分量的节点权值之和的最小值。 在第一次 DFS 中,我们可以使用一个变量 maxsubsize 来记录以当前节点为根的子树中,最大的子树大小。我们可以使用一个变量 totsize 来记录以当前节点为根的子树中,所有节点的总数。我们可以使用一个变量 cursum 来记录以当前节点为根的子树中,所有节点的权值之和。我们可以使用一个变量 curans 来记录删除当前节点后,剩余各个连通分量的节点数最大值。具体来说,我们可以枚举当前节点的每个子节点,然后递归地计算以该子节点为根的子树中,最大的子树大小。我们可以使用一个变量 cursize 来记录以该子节点为根的子树中,所有节点的总数。我们可以使用一个变量 subsum 来记录以该子节点为根的子树中,所有节点的权值之和。然后,我们可以使用 maxsubsize 来更新以当前节点为根的子树中,最大的子树大小。我们可以使用 totsize 来更新以当前节点为根的子树中,所有节点的总数。我们可以使用 cursum 来更新以当前节点为根的子树中,所有节点的权值之和。最后,我们可以使用一个变量 maxsize 来记录当前节点的父节点到当前节点这条路径上,最大的子树大小。我们可以使用一个变量 parentsize 来记录当前节点的父节点的子树大小。然后,我们可以使用 maxsize 和 totsize - cursize 来计算删除当前节点后,剩余各个连通分量的节点数最大值。最后,我们可以使用 curans 来更新 ans。 在第二次 DFS 中,我们可以使用一个变量 maxsubsize 来记录以当前节点为根的子树中,最大的子树大小。我们可以使用一个变量 totsize 来记录以当前节点为根的子树中,所有节点的总数。我们可以使用一个变量 cursum 来记录以当前节点为根的子树中,所有节点的权值之和。我们可以使用一个变量 parentsize 来记录当前节点的父节点的子树大小。具体来说,我们可以枚举当前节点的每个子节点,然后递归地计算以该子节点为根的子树中,最大的子树大小。我们可以使用一个变量 cursize 来记录以该子节点为根的子树中,所有节点的总数。我们可以使用一个变量 subsum 来记录以该子节点为根的子树中,所有节点的权值之和。然后,我们可以使用 maxsubsize 来更新以当前节点为根的子树中,最大的子树大小。我们可以使用 totsize 来更新以当前节点为根的子树中,所有节点的总数。我们可以使用 cursum 来更新以当前节点为根的子树中,所有节点的权值之和。最后,我们可以使用 parentsize 和 totsize - cursize 来计算删除当前节点后,剩余各个连通分量的节点数最大值。如果当前节点不是树的重心,那么我们可以使用 ans 来更新剩余各个连通分量的节点权值之和的最小值。 最后,我们可以返回 ans。 Java 代码: class Solution { int[] subsize; int[] sum; int ans = Integer.MAX_VALUE; public int getCenter(int[][] edges, int[] weight) { int n = weight.length; subsize = new int[n]; sum = new int[n]; dfs1(, -1, edges, weight); dfs2(, -1, edges, weight); return ans; } private void dfs1(int u, int p, int[][] edges, int[] weight) { subsize[u] = 1; sum[u] = weight[u]; int maxsubsize = ; int totsize = 1; int cursum = weight[u]; int curans = ; for (int v : edges[u]) { if (v == p) { continue; } dfs1(v, u, edges, weight); int cursize = subsize[v]; int subsum = sum[v]; subsize[u] += cursize; sum[u] += subsum; maxsubsize = Math.max(maxsubsize, cursize); totsize += cursize; cursum += subsum; int maxsize = Math.max(cursize, subsize[u] - cursize); int parentsize = totsize - cursize; curans = Math.max(curans, Math.min(maxsize, parentsize)); } int maxsize = Math.max(maxsubsize, totsize - maxsubsize); if (maxsize < ans) { ans = maxsize; } } private void dfs2(int u, int p, int[][] edges, int[] weight) { subsize[u] = 1; sum[u] = weight[u]; int maxsubsize = ; int totsize = 1; int cursum = weight[u]; int parentsize = p == -1 ? : subsize[p]; for (int v : edges[u]) { if (v == p) { continue; } int cursize = subsize[v]; int subsum = sum[v]; subsize[u] += cursize; sum[u] += subsum; maxsubsize = Math.max(maxsubsize, cursize); totsize += cursize; cursum += subsum; int maxsize = Math.max(cursize, subsize[u] - cursize); int childsize = totsize - cursize; int curans = Math.max(Math.min(maxsize, parentsize + childsize), Math.min(subsize[v], totsize - subsize[v])); if (curans < ans) { ans = curans; } dfs2(v, u, edges, weight); } } }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农研究僧

你的鼓励将是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值