剑指offer②刷题1-20

125 阅读3分钟

1、整数除法

class Solution {
    public int divide(int a, int b) {
       if(a==0) return 0;
       if(a==b) return 1;
       if(a==Integer.MIN_VALUE&&b==-1) return Integer.MAX_VALUE;
       int res = 0;
       //用flag标志算式最终符号,将a、b转换成负数进行计算
       int flag = (a>0)^(b>0)?-1:1;
       if(a>0) a=-a;
       if(b>0) b=-b;
       while(a<=b) {
           //相减的次数就是最终答案
           a = a - b;
           ++res;
       }
       res = (flag==1)? res:-res;
       return res; 
    }
}

2、二进制加法

class Solution {
    public String addBinary(String a, String b) {
        StringBuilder stringBuilder=new StringBuilder();
        int i=a.length()-1,j=b.length()-1;
        //定义一个临时值,存储两个字符的进位
        int temp=0;
        while (i>=0||j>=0){
            //判断是否到了a字符串中的字符是否全部相加完,全部相加完就直接返回0,没有直接返回该字符串的Ascll码值
            int a1=i>=0?a.charAt(i--)-'0':0;
            //判断是否到了b字符串中的字符是否全部相加完,全部相加完就直接返回0,没有直接返回该字符串的Ascll码值
            int b1=j>=0?b.charAt(j--)-'0':0;
            //求两个字符和进位的和
            int sum=a1+b1+temp;
            //如果和大于等于2,进位为1,否则为0
            temp=sum>=2?1:0;
            //如果和大于等于2,将和减2,否则为原值
            sum=sum>=2?sum-2:sum;
            //将sum加入stringBuilder中;
            stringBuilder.append(sum);
        }
        //当两个字符串的索引都小于0,将最后的进位加入stringBuilder
        if (temp==1){
            stringBuilder.append(1);
        }
        //将stringBuilder反向遍历,并返回
        return stringBuilder.reverse().toString();
    }
}

3、前 n 个数字二进制中 1 的个数

class Solution {
    public int[] countBits(int n) {
        //新建一个数组,大小n+1,用于存放[0,n]的所有结果,初始res[0]=0
        //当i为偶数,i的二进制位数和i>>1相同,i为奇数,二进制位数等于i>>1+1,比如i=5,res[5] = res[5/2]+1=res[2]+1=2
        //最后把结果数组返回
        int[] res = new int[n+1];
        res[0] = 0;
        for(int i = 1; i <= n; i++){
            res[i] = res[i>>1] + i % 2;
        }
        return res;
    }
}

4、只出现一次的数字

位运算,采用二进制,将每一位的数字相加,对3取余不为零就记录

class Solution {
    public int singleNumber(int[] nums) {
        int ret = 0;
        for (int i = 0; i < 32; i++) {
            int cnt = 0;
            for (int num : nums) {
                //取最后一位数字,各位相加
                cnt += num >> i & 1;
            }
            if (cnt % 3 != 0) {
                ret += 1 << i;
            }
        }
        return ret;
    }
}

5、单词长度的最大乘积

a|=b的意思就是把a和b按位或然后赋值给a。 按位或的意思就是先把a和b都换成2进制,然后用或操作,相当于a=a|b。

class Solution {
    public int maxProduct(String[] words) {
        int length = words.length;
        int[] masks = new int[length];
        for (int i = 0; i < length; i++) {
            String word = words[i];
            int wordLength = word.length();
            for (int j = 0; j < wordLength; j++) {
                char c = word.charAt(j);
                //二进制表示从低到高的第 0 位到第 25 位的每一位分别表示单词中是否出现 a 到 z 的每个字母,如果一个二进制位是 1 则表示该位对应的字母在单词中出现,如果一个二进制位是 0 则表示该位对应的字母不在单词中出现。
                masks[i] |= 1 << (c - 'a');
            }
        }
        int maxValue = 0;
        for (int i = 0; i < length; i++) {
            for (int j = i + 1; j < length; j++) {
                //如果按位与运算结果是 0,则这两个单词不含有公共字母
                if ((masks[i] & masks[j]) == 0) {
                    int value = words[i].length() * words[j].length();
                    maxValue = Math.max(maxValue, value);
                }
            }
        }
        return maxValue;
    }
}

6、排序数组中两个数字之和

解法一:二分查找

    public int[] twoSum(int[] numbers, int target) {
        for (int i = 0; i < numbers.length; ++i) {
            int low = i + 1, high = numbers.length - 1;
            while (low <= high) {
                int mid = (high - low) / 2 + low;
                if (numbers[mid] == target - numbers[i]) {
                    return new int[]{i, mid};
                } else if (numbers[mid] > target - numbers[i]) {
                    high = mid - 1;
                } else {
                    low = mid + 1;
                }
            }
        }
        return new int[]{-1, -1};
    }
}

解法二:双指针

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int low = 0, high = numbers.length - 1;
        while (low < high) {
            int sum = numbers[low] + numbers[high];
            if (sum == target) {
                return new int[]{low, high};
            } else if (sum < target) {
                ++low;
            } else {
                --high;
            }
        }
        return new int[]{-1, -1};
    }
}

7、数组中和为 0 的三个数

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        int len = nums.length;
        List<List<Integer>> res = new ArrayList<>();
        if(nums == null || len < 3) return res;
        Arrays.sort(nums);
        for(int i = 0; i < len - 2;i++){
            // 如果最小的数比0大,直接退出循环
            if (nums[i] > 0) {
                break;
            }
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            // 在 i + 1 ... nums.length - 1 中查找相加等于 -nums[i] 的两个数
            int target = -nums[i];
            int left = i + 1;
            int right = len - 1;
            while(left < right){
                int sums = nums[left] + nums[right];
                if(target == sums){
                    res.add(Arrays.asList(nums[i],nums[left],nums[right]));
                    left++;
                    right--;
                    // 去重
                    while (left < right && nums[left] == nums[left - 1]) {
                        left++;
                    }
                    while (left < right && nums[right] == nums[right + 1]) {
                        right--;
                    }
                }else if(sums < target){
                    left++;
                }else{
                    right--;
                }
            }
        }
        return res;
    }
}

8、和大于等于 target 的最短子数组

class Solution {
    public int minSubArrayLen(int s, int[] nums) {
        int n = nums.length;
        if (n == 0) {
            return 0;
        }
        int ans = Integer.MAX_VALUE;
        // start 和 end 分别表示子数组(滑动窗口窗口)的开始位置和结束位置,维护变量 sum 存储子数组中的元素和(即从 nums[start] 到 nums[end] 的元素和)
        int start = 0, end = 0;
        int sum = 0;
        while (end < n) {
            sum += nums[end];
            while (sum >= s) {
                ans = Math.min(ans, end - start + 1);
                sum -= nums[start];
                start++;
            }
            end++;
        }
        return ans == Integer.MAX_VALUE ? 0 : ans;
    }
}

9、乘积小于 K 的子数组

class Solution {
    public int numSubarrayProductLessThanK(int[] nums, int k) {
        int left = 0;
        int ret = 0;
        int total = 1;
        for (int right = 0; right < nums.length; right++) {
            total *= nums[right];
            while (left <= right && total >= k) {
                total /= nums[left++];
            }
            if (left <= right) {
                //窗口每次移动后,ret都可以增加 right - left + 1个子数组
                ret += right - left + 1;
            }
        }
        return ret;
    }
}

10、和为 k 的子数组

class Solution {
    public int subarraySum(int[] nums, int k) {
       //设sum[i]表示[0,i)的和,题目转换为求sum[end] - sum[start]=k的个数,转换为两数之差的个数
       int sum = 0;
       int count = 0;
       //存储sum[end]-k对应的个数
       Map<Integer, Integer> map = new HashMap();
       map.put(0, 1);
       for(int i = 0; i < nums.length; i++){
           sum += nums[i];
           //sum[end]-k=sum[start],从end查start的个数,不能从start查end的个数
           count += map.getOrDefault(sum - k, 0);
           //为后边计数
           map.put(sum, map.getOrDefault(sum, 0) + 1);
       }
       return count;
    }
}

11、0 和 1 个数相同的子数组

class Solution {
    public int findMaxLength(int[] nums) {
        HashMap<Integer, Integer> map = new HashMap<>();
        // map存储的是下标,假设中间计算出前10个元素和为0,那么子数组长度为9-(-1)=10, 9是当前遍历数组下标
        map.put(0, -1);
        int pre_sum = 0;
        int ret = 0;
        for (int i = 0; i < nums.length; i++) {
            // 将数组中的0换成-1,求和为0的最长子数组,转换成前缀和问题
            pre_sum += nums[i] == 0 ? -1 : 1;
            // 判断哈希表中是否存在值为pre_sum的key
            if (map.containsKey(pre_sum)) {
                // 若存在pre_sum的key,更新ret为max(ret, 当前下标 - key对应的value + 1)
                ret = Math.max(ret, i - map.get(pre_sum));
            } else {
                map.put(pre_sum, i);
            }
        }
        return ret;
    }
}

12、左右两边子数组的和相等

class Solution {
    public int pivotIndex(int[] nums) {
        int total = Arrays.stream(nums).sum();
        int sum = 0;
        for (int i = 0; i < nums.length; ++i) {
            if (2 * sum + nums[i] == total) {
                return i;
            }
            sum += nums[i];
        }
        return -1;
    }
}

13、二维子矩阵的和

class NumMatrix {
    //辅助矩阵,坐标sums[row + 1][col + 1]存入的是matrix[0][0]到matrix[row][col]矩阵的和
    private int[][] sums;
    //初始化辅助矩阵,为方便计算,sums行列数各+1
    public NumMatrix(int[][] matrix) {
        if (matrix.length == 0 || matrix[0].length == 0) {
            return;
        }
        //遍历计算辅助矩阵,每行的值为上一个单元格的值加当前行的和
        sums = new int[matrix.length + 1][matrix[0].length + 1];
        for (int i = 0; i < matrix.length; i++) {
            //记录每行元素的前缀和
            int rowSum = 0;
            for (int j = 0; j < matrix[0].length; j++) {
                rowSum += matrix[i][j];
                sums[i+1][j+1] = sums[i][j + 1] + rowSum;
            }            
        }
    }
    //使用辅助矩阵计算指定矩阵区域的和
    public int sumRegion(int row1, int col1, int row2, int col2) {
        return sums[row2 + 1][col2 + 1] - sums[row1][col2 + 1] - sums[row2 + 1][col1] + sums[row1][col1];
    }
}

/**
 * Your NumMatrix object will be instantiated and called as such:
 * NumMatrix obj = new NumMatrix(matrix);
 * int param_1 = obj.sumRegion(row1,col1,row2,col2);
 */

14、字符串中的变位词?

class Solution {
    public boolean checkInclusion(String s1, String s2) {
        int len1 = s1.length(), len2 = s2.length();
        if(len1 > len2) return false;
        // charDiffs统计每一个字符的出现(++)和消失(--)
        int[] charDiffs = new int[26];
        // 统计有差异的字符的个数,取值范围[0, 25]
        int diffCounts = 0;
        // 考察第一个窗口,并得到考察后的charDiffs[]
        for(int i = 0; i < len1; i++) {
            charDiffs[s1.charAt(i) - 'a']--;
            charDiffs[s2.charAt(i) - 'a']++;
        }
        // 考察第一个窗口是否为变位词
        for(int charDiff : charDiffs) {
            if(charDiff != 0) diffCounts++;
        }
        // 是则直接返回true
        if(diffCounts == 0) return true;
        // 否则滑动窗口,每次滑动一位
        for(int i = len1; i < len2; i++) {
            int removed = s2.charAt(i - len1) - 'a';
            int added = s2.charAt(i) - 'a';
            // 离开窗口和进入窗口的字符相同,跳过
            if(removed == added) continue;
            // 关于removed的字符的差异为0时,因为removed离开,导致字符种类差分+1,且在charDiffs上该字符差分--
            if(charDiffs[removed] == 0) diffCounts++;
            charDiffs[removed]--; // 离开
            // 因为有上一行的离开动作,需要再次确认该字符的差异数
            if(charDiffs[removed] == 0) diffCounts--;
            // 关于added的字符的差异为0时,因为added加入,导致字符种类差分+1,且在charDiffs上该字符差分++
            if(charDiffs[added] == 0) diffCounts++;
            charDiffs[added]++; // 进入
            // 因为有上一行的进入动作,需要再次确认该字符的差异数
            if(charDiffs[added] == 0) diffCounts--;
            if(diffCounts == 0) return true;
        }
        return false;
    }
}

15、字符串中的所有变位词?

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        int sLen = s.length(), pLen = p.length();
        if (sLen < pLen) {
            return new ArrayList<Integer>();
        }
        List<Integer> ans = new ArrayList<Integer>();
        int[] sCount = new int[26];
        int[] pCount = new int[26];
        for (int i = 0; i < pLen; ++i) {
            ++sCount[s.charAt(i) - 'a'];
            ++pCount[p.charAt(i) - 'a'];
        }
        if (Arrays.equals(sCount, pCount)) {
            ans.add(0);
        }
        for (int i = 0; i < sLen - pLen; ++i) {
            --sCount[s.charAt(i) - 'a'];
            ++sCount[s.charAt(i + pLen) - 'a'];

            if (Arrays.equals(sCount, pCount)) {
                ans.add(i + 1);
            }
        }
        return ans;
    }
}

16、不含重复字符的最长子字符串

class Solution {
    public int lengthOfLongestSubstring(String s) {
        // 哈希集合,记录每个字符是否出现过
        Set<Character> num = new HashSet<Character>();
        int n = s.length();
        // 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
        int right = -1, ans = 0;
        for (int left = 0; left < n; left++) {
            if (left != 0) {
                // 左指针向右移动一格,移除一个字符
                num.remove(s.charAt(left - 1));
            }
            while (right + 1 < n && !num.contains(s.charAt(right + 1))) {
                // 不断地移动右指针
                num.add(s.charAt(right + 1));
                right++;
            }
            //leftright 个字符是一个极长的无重复字符子串
            ans = Math.max(ans, right - left + 1);
        }
        return ans;
    }
}

17、含有所有字符的最短字符串?

class Solution {
    public String minWindow(String s, String t) {
        int len1 = s.length(),len2 = t.length();
        //输出的字符串,初始为空
        String res = "";
        if(len1<len2) return res;
        //t1为t的数组,s1为s的数组
        int[] t1 = new int[100];
        int[] s1 = new int[100];
        for(int i=0;i<len2;i++){
            t1[t.charAt(i)-'A']++;
        }
        //diff标记不同,当为0时代表已找到
        int diff = 0;
        for(int i:t1){
            if(i!=0) diff++;
        }
        //left,right双指针
        int left = 0;
        int min=Integer.MAX_VALUE;
        for(int right=0;right<len1;right++){
            //s1[x]++后如果等于t1[x],代表字符已全部找到
            int x = s.charAt(right)-'A';
            s1[x]++;
            if(t1[x]==s1[x]) diff--;
            while(diff==0){
                //对比赋值res
                int l = right-left+1;
                if(l<min){
                    min=l;
                    res=s.substring(left,right+1);
                }
                //t1[y]!=0表示 t 含有此字符,只要s1[y]的值不小于t1[y],那么结果不影响,如果小于则diff++;
                int y = s.charAt(left)-'A';
                s1[y]--;
                if(t1[y]!=0&&s1[y]<t1[y]){
                    diff++;
                }
                left++;
            }
        }
        return res;
    }
}

18、有效的回文

class Solution {
    public boolean isPalindrome(String s) {
        int left = 0;
        int right = s.length() - 1;
        // 利用双指针思想向字符串中间逼近
        while (left <= right) {
            if (!Character.isLetterOrDigit(s.charAt(left))) {
                left += 1;
            } else if (!Character.isLetterOrDigit(s.charAt(right))) {
                right -= 1;
            } else {
                char char1 = Character.toLowerCase(s.charAt(left++));
                char char2 = Character.toLowerCase(s.charAt(right--));
                if (char1 != char2) {
                    return false;
                }
            }
        }
        return true;
    }
}

19、最多删除一个字符得到回文

class Solution {
    public boolean validPalindrome(String s) {
        for(int left = 0, right = s.length() - 1; left < right; left++, right--){
            // 如果不相等,则分两种情况:删除左边的元素,或者右边的元素,再判断各自是否回文,满足一种即可。
            if(s.charAt(left) != s.charAt(right)) 
                return isPalindrome(s, left+1, right) || isPalindrome(s, left, right - 1);
        }
        return true;
    }
    // 判断字符串 s 的 [left, right] 是否回文
    private boolean isPalindrome(String s, int left , int right){
        while (left < right){
            if (s.charAt(left++) != s.charAt(right--))
                return false;
        }
        return true;
    }
}

20、回文子字符串的个数

class Solution {
    public int countSubstrings(String s) {
        if (s == null || s.length() == 0) {
            return 0;
        }
        int count = 0;
        //字符串的每个字符都作为回文中心进行判断,中心是一个字符或两个字符
        for (int i = 0; i < s.length(); ++i) {
            count += countPalindrome(s, i, i);
            count += countPalindrome(s, i, i+1);
        }
        return count;
    }
    //从字符串的第start位置向左,end位置向右,比较是否为回文并计数
    private int countPalindrome(String s, int start, int end) {
        int count = 0;
        while (start >= 0 && end < s.length() && s.charAt(start) == s.charAt(end)) {
            count++;
            start--;
            end++;
        }
        return count;
    }
}