42.接雨水:https://leetcode-cn.com/problems/trapping-rain-water/

点击查看代码
public class Solution {
    public int trap(int[] height) {
        int size = height.length;
        if (size <= 1) {
            return 0;
        }

        int[] lh = new int[size];
        int[] rh = new int[size];
        lh[0] = 0;
        for (int i = 1; i < size; i++) {
            lh[i] = Math.max(lh[i - 1], height[i-1]);
        }
        rh[size - 1] = 0;
        for (int i = size - 2; i >= 0; i--) {
            rh[i] = Math.max(rh[i + 1], height[i+1]);
        }

        int sum = 0;
        for (int i = 1; i <= size - 2; i++) {
            // 第i列可装雨水量为左边或右边最高度-当前列的高度:max(0, min(lh, rh) - curh)
            sum += Math.max(Math.min(lh[i], rh[i]) - height[i], 0);
        }
        return sum;
    }
}
  1. 整数拆分:https://leetcode-cn.com/problems/integer-break/
点击查看代码
// 找规律,整数拆分结果只包含2和3,2和3的个数具有一定规律和关联。时间复杂度为O(1)
public class Solution {
    int integerBreak(int n) {
        if (n < 4) {
            return n - 1;
        }

        int count3 = n / 3;
        if (n % 3 == 1) {
            count3--;
        }
        int count2 = (n - 3 * count3) / 2;
        return (int) (Math.pow(3, count3) * Math.pow(2, count2));
    }
}
// 子问题拆分。分析:f(n) = max(f(i) * f(n-i)), i=1……n/2,分析时自上而下,求解时自下而上。时间复杂度O(n^2)
public class Solution {
    public int integerBreak(int n) {
        if (n < 4) {
            return n - 1;
        }
        int[] f = new int[n + 1];
        // 自下而上求解,当n大于4时,分段1、2、3无需再拆解已是最大值
        f[1] = 1;
        f[2] = 2;
        f[3] = 3;
        for (int i = 4; i <= n; i++) {
            int res = 0;
            for (int j = 1; j <= i / 2; j++) {
                res = Math.max(res, f[j] * f[i - j]);
            }
            f[i] = res;
        }
        return f[n];
    }
}
  1. 柠檬水找零:https://leetcode-cn.com/problems/lemonade-change/
点击查看代码
public class Solution {
    public boolean lemonadeChange(int[] bills) {
        int bill5 = 0;
        int bill10 = 0;
        for (int bill : bills) {
            if (bill == 5) {
                bill5++;
            } else if (bill == 10) {
                if (bill5 <= 0) {
                    return false;
                }
                bill5--;
                bill10++;
            } else {
                // 收到20元时,优先找10元,没有时再全部使用5元
                if (bill10 > 0 && bill5 > 0) {
                    bill10--;
                    bill5--;
                } else if (bill5 >= 3) {
                    bill5 -= 3;
                } else {
                    return false;
                }
            }
        }
        return true;
    }
}
  1. 有序数组的平方:https://leetcode-cn.com/problems/squares-of-a-sorted-array/
点击查看代码
// 本题使用hash数组;由于原数组有序,本题也可使用双指
class Solution {
    public int[] sortedSquares(int[] nums) {
        int maxSize = 10001;
        // 创建哈希数组,tmp[i]表示nums数组中取值为i的元素个数
        int[] tmp = new int[maxSize];
        for (int num : nums) {
            if (num < 0) {
                num = -num;
            }
            tmp[num]++;
        }

        int j = 0;
        for (int i = 0; i < maxSize; i++) {
            if (tmp[i] == 0) {
                continue;
            }
            while (tmp[i] > 0) {
                nums[j++] = i * i;
                tmp[i]--;
            }
        }
        return nums;
    }
}
// 双指针代码参考:https://blog.csdn.net/weixin_44029692/article/details/107050722
  1. 数组中的 k-diff 数对:https://leetcode-cn.com/problems/k-diff-pairs-in-an-array/
点击查看代码
// 排序+双指针
public class Solution {
    public int findPairs(int[] nums, int k) {
        if (nums.length == 1) {
            return 0;
        }
        Arrays.sort(nums);

        int res = 0;
        int pre = 0;
        int post = 1;
        // 因为要求数组对不重复,这里需要标记一下上一个数组对
        int preNum = Integer.MIN_VALUE;
        
        while (post < nums.length) {
            if (nums[post] - nums[pre] == k) {
                // 避免nums中元素重复导致数组对重复统计
                if (nums[pre] != preNum) {
                    res++;
                    preNum = nums[pre];
                }
                pre++;
                post++;
            } else if (nums[post] - nums[pre] > k) {
                // 移动pre指针,不能超过post指针
                if (pre + 1 == post) {
                    post++;
                } 
                pre++;
            } else {
                // 移动post指针
                post++;
            }
        }
        return res;
    }
}
// 方法2,借助HashMap去重,时间复杂度O(n)
public class Solution {
    public int findPairs(int[] nums, int k) {
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            // 记录元素值和下标
            map.put(nums[i], i);
        }

        ArrayList<Pair<Integer, Integer>> res = new ArrayList<>();
        for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
            int tmp = k - entry.getKey();
            // 确保k和tmp都在map中且不是nums中的同一个元素
            if (map.containsKey(tmp) && !map.get(tmp).equals(entry.getValue())) {
                res.add(new Pair<>(entry.getValue(), map.get(tmp)));
            }
        }
        // (k,tmp)及(tmp,k)都在res中,如果不需要数据具体数组对,可不用ArrayList,直接统计符合条件的数组对个数
        return res.size()/2;
    }
}
  1. 合并两个有序数组:https://leetcode-cn.com/problems/merge-sorted-array/
点击查看代码
// 双指针:有序数组原地合并
public class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int i = m - 1;
        int j = n - 1;
        int curLoc = m + n - 1;
        while (i >= 0 && j >= 0) {
            if (nums1[i] > nums2[j]) {
                nums1[curLoc--] = nums1[i--];
            } else {
                nums1[curLoc--] = nums2[j--];
            }
        }
        // 不用再单独处理i >= 0的场景
        while (j >= 0) {
            nums1[curLoc--] = nums2[j--];
        }
    }
}
  1. 跳跃游戏:https://leetcode-cn.com/problems/jump-game/
点击查看代码
// 处于位置i时判断: i及之前的位置所能跳的最远距离,如果落在i,则false,如果超过n-1,则true,否则继续
public class Solution {
    public boolean canJump(int[] nums) {
        int rightMost = 0;
        // 最后一个位置不用再判断,所以是nums.length-1
        for (int i = 0; i < nums.length - 1; i++) {
            rightMost = Math.max(rightMost, i + nums[i]);
            if (rightMost == i) {
                return false;
            } else if (rightMost >= nums.length - 1) {
                return true;
            }
        }
        return true;
    }
}
  1. 排序数组,手写归并排序、快排、堆排:https://leetcode-cn.com/problems/sort-an-array/
点击查看代码
// 归并排序实现
public class Solution {
    public int[] sortArray(int[] nums) {
        if (nums.length <= 1) {
            return nums;
        }
        // 归并
        mergeSort(nums, 0, nums.length - 1);
        return nums;
    }

    public void mergeSort(int[] nums, int low, int high) {
        if (low >= high) {
            return;
        }
        int mid = (low + high) / 2;
        // 向下划分
        mergeSort(nums, low, mid);
        mergeSort(nums, mid + 1, high);
        // 向上合并
        merge(nums, low, mid, high);
    }

    public void merge(int[] nums, int low, int mid, int high) {
        int[] tmp = new int[high - low + 1];
        int cur = 0;

        // 合并有序数组
        int i = low;
        int j = mid + 1;
        while (i <= mid && j <= high) {
            if (nums[i] <= nums[j]) {
                tmp[cur++] = nums[i++];
            } else {
                tmp[cur++] = nums[j++];
            }
        }
        while (i <= mid) {
            tmp[cur++] = nums[i++];
        }
        while (j <= high) {
            tmp[cur++] = nums[j++];
        }

        // 复制临时数组到nums
        for (int k = 0; k < tmp.length; k++) {
            nums[low + k] = tmp[k];
        }
    }
}

// 堆排实现:堆本质上时一个完全二叉树,底层数据结构使用数组即可
// 建堆:由下向上排序;维护堆:新数据覆盖堆顶,由上向下调整
public class Solution {
    public int[] sortArray(int[] nums) {
        // 建大顶堆,由下向上调整
        for (int i = 1; i < nums.length; i++) {
            adjustDownToUp(i, nums);
        }
        // 维护堆,将堆顶元素和后面的元素交换,再维护[0,n-1]区间的大顶堆,直到整个数组升序排序完成
        for (int j = nums.length - 1; j > 0; j--) {
            // 元素交换
            swap(0, j, nums);
            // 剩下区间维护大顶堆。由上向下调整
            adjustUpToDown(j - 1, nums);
        }
        return nums;
    }

    public void adjustDownToUp(int numMax, int[] nums) {
        int par = numMax;
        while (par > 0) {
            // 依次找出父节点,左孩子,右孩子,并比较(符合大顶堆定义)。由下xiang
            par = (par - 1) / 2;
            int lchild = par * 2 + 1;
            int rchild = par * 2 + 2;

            // tmp用以保存取值较大的孩子编号,左孩子一定存在
            int tmp = lchild;
            if (rchild <= numMax && nums[lchild] < nums[rchild]) {
                tmp = rchild;
            }
            // 如果不需要交换,则已经满足堆定义,直接break
            if (nums[tmp] <= nums[par]) {
                break;
            }
            swap(tmp, par, nums);
        }
    }

    public void adjustUpToDown(int numMax, int[] nums) {
        int par = 0;
        while (par < numMax) {
            // 依次找出父节点,左孩子,右孩子,并比较(符合大顶堆定义)
            int lchild = par * 2 + 1;
            int rchild = par * 2 + 2;
            if (lchild > numMax) {
                break;
            }

            // tmp用以保存取值较大的孩子编号,左孩子一定存在
            int tmp = lchild;
            if (rchild <= numMax && nums[tmp] < nums[rchild]) {
                tmp = rchild;
            }
            // 如果不需要交换,则已经满足堆定义,直接break
            if (nums[tmp] <= nums[par]) {
                break;
            }
            swap(tmp, par, nums);
            par = tmp;
        }
    }

    public void swap(int i, int j, int[] nums) {
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
}

// 快排实现
public class Solution {
    public int[] sortArray(int[] nums) {
        if (nums.length <= 1) {
            return nums;
        }
        // 快排
        quickSort(nums, 0, nums.length - 1);
        return nums;
    }

    public void quickSort(int[] nums, int low, int high) {
        int i = low;
        int j = high;
        if (i < j) {
            // 选择支点
            int pivot = nums[i];
            while (i < j) {
                while (i < j && nums[j] >= pivot) {
                    j--;
                }
                nums[i] = nums[j];
                while (i < j && nums[i] < pivot) {
                    i++;
                }
                nums[j] = nums[i];
            }
            nums[i] = pivot;
            quickSort(nums, low, i - 1);
            quickSort(nums, i + 1, high);
        }
    }
}
  1. 最大间距:https://leetcode-cn.com/problems/maximum-gap/
点击查看代码
// 题目要求时间复杂度为O(n),题解:桶排+鸽笼原理
// 鸽笼原理:https://blog.csdn.net/m0_37433111/article/details/108401807
// 鸽笼原理结论: maxGap >= avgGap = bucketGap
public class Solution {
    public int maximumGap(int[] nums) {
        if (nums.length <= 1) {
            return 0;
        }

        // 获取最大值和最小值
        int minNum = Arrays.stream(nums).min().getAsInt();
        int maxNum = Arrays.stream(nums).max().getAsInt();
        if (maxNum == minNum) {
            return 0;
        }
        // 获取桶的长度(桶内元素的取值范围区间)
        int bkgap = (maxNum - minNum) / nums.length + 1;
        // 获取桶的个数
        int bknum = (maxNum - minNum) / bkgap + 1;
        // 创bknum个桶,每个桶只记录最大值和最小值,因此桶内元素个数为2,初始值均为-1
        int[][] buckets = new int[bknum][2];
        for (int i = 0; i < bknum; i++) {
            Arrays.fill(buckets[i], -1);
        }
        // 将nums中的数放到各个桶中(实际只更新最大值和最小值)
        for (int num : nums) {
            int loc = (num - minNum) / bkgap;
            if (buckets[loc][0] == -1) {
                buckets[loc][0] = buckets[loc][1] = num;
            } else {
                buckets[loc][0] = Math.min(buckets[loc][0], num);
                buckets[loc][1] = Math.max(buckets[loc][1], num);
            }
        }

        // 最大间距 = 下一个有元素的桶的最小值 - 上一个有元素的桶的最大值
        int res = Integer.MIN_VALUE;
        int pre = -1;
        for (int curLoc = 0; curLoc < bknum; curLoc++) {
            if (buckets[curLoc][0] == -1) {
                // 桶内没有元素
                continue;
            }
            if (pre != -1) {
                res = Math.max(res, buckets[curLoc][0] - buckets[pre][1]);
            }
            pre = curLoc;
        }
        return res;
    }
}

剑指 Offer 57 - II. 和为s的连续正数序列:https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/

点击查看代码
// 双指针维护一个滑动窗口区间值,判断该区间值的和是否==target
class Solution {
    public int[][] findContinuousSequence(int target) {
        ArrayList<int[]> res = new ArrayList<>();
        
        int pre = 1;
        int post = 2;
        // 有序序列区间[pre, post]的和
        int sum = 3; 
        while (post <= target / 2 + 1) {
            if (sum == target) {
                int[] tmp = new int[post - pre + 1];
                for (int i = pre; i <= post; i++) {
                    tmp[i - pre] = i;
                }
                res.add(tmp);
                post++;
                sum += post;
            } else if (sum < target) {
                post++;
                sum += post;
            } else {
                sum -= pre;
                // 某些场景移动pre指针后,需要判断并处理pre >= post的场景
                pre++;
            }
        }
        return res.toArray(new int[res.size()][]);
    }
}

//用双指针维护滑动区间值 场景变形:某一个大文件被拆成了N个小文件,每个小文件编号从0至N-1,相应大小分别记为S(i)。给定磁盘空间为C,试实
//现一个函数从N个文件中连续选出若干个文件拷贝到磁盘中,使得磁盘剩余空间最小。
  1. 最长连续序列:https://leetcode-cn.com/problems/longest-consecutive-sequence/
点击查看代码
// 思路1:先排序再求解,或者直接使用TreeSet,时间复杂度均为O(nLogn)
// 正确题解:使用HashSet,找到每个序列开始的元素,再统计长度
public class Solution {
    public int longestConsecutive(int[] nums) {
        if (nums.length <= 1) {
            return nums.length;
        }
        // HashSet和HashMap可去重,元素无序,查找某元素的时间复杂度为O(1)
        HashSet<Integer> set = new HashSet<>();
        for (int num : nums) {
            set.add(num);
        }

        int res = 0;
        for (int num : set) {
            // 判断num是否为连续序列的第一个数字,这是O(n)时间复杂度的关键
            if (!set.contains(num - 1)) {
                int curNum = num;
                int curLen = 1;
                while (set.contains(curNum + 1)) {
                    curNum++;
                    curLen++;
                }
                res = Math.max(res, curLen);
            }
        }
        return res;
    }
}
  1. 前 K 个高频元素:https://leetcode-cn.com/problems/top-k-frequent-elements/
点击查看代码
public class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        // 统计各数出现的频次
        HashMap<Integer, Integer> map = new HashMap();
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 1));
        }

        // 使用小顶堆维护频次最高的前k个数, 默认是大顶堆
        PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return map.get(o1) - map.get(o2);
            }
        });
        // 直接遍历map(去重+统计频次)
        for (int num : map.keySet()) {
            if (pq.size() < k) {
                pq.add(num);
            } else if (map.get(num) > map.get(pq.peek())) {
                // 堆顶元素出现频次小于当前元素的频次
                pq.remove();
                pq.add(num);
            }
        }

        int[] res = new int[pq.size()];
        int len = 0;
        while (!pq.isEmpty()) {
            res[len++] = pq.remove();
        }
        return res;
    }
}
  1. 下一个排列:https://leetcode-cn.com/problems/next-permutation/
点击查看代码
/ 输出给定数字下一个比它大的数字。java中的util类:Arrays, Collections。数学类:Math
// 求解思路: 从后往前找转折点,满足:num[i] < num[i+1]. 然后从i+1往后找比num[i]大的所有数中最小的数num[j].
// 交换num[i]和num[j]后通过排序是的num[i+1]之后的数由小到大排序
public class Solution {
    public void nextPermutation(int[] nums) {
        if (nums.length <= 1) {
            return;
        }

        // 查找转折点i
        int i = nums.length - 2;
        while (i >= 0 && nums[i] >= nums[i+1]) {
            i--;
        }
        if (i < 0) {
            // 当前数已经最大,直接反转
            Arrays.sort(nums);
            return;
        }

        // 查找可交换的元素j.注意:i+1之后的元素一定为递减序列,因此从后往前找即可
        int j = nums.length - 1;
        while (nums[j] <= nums[i]) {
            j--;
        }

        // 交换元素num[i]和num[j]
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;

        // 确保i+1之后的元素递增
        Arrays.sort(nums, i+1, nums.length);
    }
}
  1. 字符串的排列:https://leetcode-cn.com/problems/permutation-in-string/
点击查看代码
// 时间复杂度为O(n)
class Solution {
    public boolean checkInclusion(String s1, String s2) {
        if (s1.length() > s2.length()) {
            return false;    
        }
        
        int size = 26;
        int[] s1Arr = new int[size];
        int[] s2Arr = new int[size];

        for (int i = 0; i < s1.length(); i++) {
            s1Arr[s1.charAt(i) - 'a']++;
            s2Arr[s2.charAt(i) - 'a']++;
        }
        if (isSame(s1Arr, s2Arr)) {
            return true;
        }
        for (int j = s1.length(); j < s2.length(); j++) {
            // 统计s2中长度为s1.length的连续子串,并比较
            // 去掉首个字符,新增一个字符
            s2Arr[s2.charAt(j - s1.length()) - 'a']--;
            s2Arr[s2.charAt(j) - 'a']++;
            if (isSame(s1Arr, s2Arr)) {
                return true;
            }
        }
        return false;
    }

    public boolean isSame(int[] s1Arr, int[] s2Arr) {
        // 比较s1Arr和s2Arr中的数据是否一致
        for (int i = 0; i < s1Arr.length; i++) {
            if (s1Arr[i] != s2Arr[i]) {
                return false;
            }
        }
        return true;
    }
}