Skip to content
字数
2180 字
阅读时间
11 分钟

数组

查找:运用 HashMap 的键的唯一性

二分查找

前提条件:数组为有序数组,且无重复元素

难点:边界条件的确定

  • 循环条件
    • while(left < right)
    • while(left <= right)
  • 右边界的更新
    • right == mid
    • rifht == mid - 1

将区间定义为 不变量,再不断循环的过程中,while 条件判断的处理要遵循 不变量规则

第一种写法

区间定义为 [left, right]

分别对应

  1. while(left <= right),因为 left == right 是有意义的
  2. if(nums[mid] > target) right 赋值为 mid - 1,因为当前的 nums[mid] 一定不是 target
java
class Solution {
    public int search(int[] nums, int target) {
        // 避免当 target 小于nums[0] nums[nums.length - 1]时多次循环运算
        if (target < nums[0] || target > nums[nums.length - 1]) {
            return -1;
        }
        int left = 0, right = nums.length - 1;
        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            if (nums[mid] == target)
                return mid;
            else if (nums[mid] < target)
                left = mid + 1;
            else if (nums[mid] > target)
                right = mid - 1;
        }
        return -1;
    }
}
title:位运算符
<< :左移运算符, num << 1,相当于 num乘以2
>> :右移运算符, num >> 1,相当于 num除以2
	正数左边第一位补0,负数补1
>>> :无符号右移运算符,无符号右移运算符和右移运算符的主要区别在于负数的计算,因为无符号右移是高位补0,移多少位补多少个0。
	15的二进制位是0000 1111 , 右移2位0000 0011,结果为3

第二种写法

区间定义为 [left, right)

分别对应

  1. while(left < right) 因为 left == right 是区间是没有意义的
  2. if(nums[mid] > target) right 更新为 right,因为 nums[mid] 的值取不到,不能确定该下标是否对应 target
java
class Solution {
    public int search(int[] nums, int target) {
        if (target < nums[0] || target > nums[nums.length - 1]) {
            return -1;
        }
        int left = 0, right = nums.length;
        while (left < right) {
            int mid = left + ((right - left) >> 1);
            if (nums[mid] == target)
                return mid;
            else if (nums[mid] < target)
                left = mid + 1;
            else if (nums[mid] > target)
                right = mid;
        }
        return -1;
    }
}

双指针法

快慢指针

移除元素

java
// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int slowIndex = 0;
        for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
            if (val != nums[fastIndex]) {
                nums[slowIndex++] = nums[fastIndex];
            }
        }
        return slowIndex;
    }
};

左右指针

# 有序数组的平方

java
// 时间复杂度为 O(n)

class Solution {
    public int[] sortedSquares(int[] nums) {
        int right = nums.length - 1;
        int left = 0;
        int[] result = new int[nums.length];
        int index = result.length - 1;
        while (left <= right) {
            if (nums[left] * nums[left] > nums[right] * nums[right]) {
                result[index--] = nums[left] * nums[left];
                ++left;
            } else {
                result[index--] = nums[right] * nums[right];
                --right;
            }
        }
        return result;
    }
}

滑动窗口

## 长度最小的子数组

主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被被操作两次,所以时间复杂度是 2 × n 也就是 O(n)
——可以理解为 start 和 end 交替前进,当 start+1 时 end 不变,反之亦然,总的时间应该是 2n,也就是 n

java
// 时间复杂度为 O(n)

class Solution {

    public int minSubArrayLen(int s, int[] nums) {
        int left = 0;
        int sum = 0;
        int result = Integer.MAX_VALUE;
        for (int right = 0; right < nums.length; right++) {
            sum += nums[right];
            while (sum >= s) {
                result = Math.min(result, right - left + 1);
                sum -= nums[left++]; // 精髓之处
            }
        }
        return result == Integer.MAX_VALUE ? 0 : result;
    }
}

三数之和

Loading Question... - 力扣(LeetCode)

两层for循环就可以确定 a 和b 的数值了,可以使用哈希法来确定 0-(a+b) 是否在 数组里出现过,其实这个思路是正确的,但是我们有一个非常棘手的问题,就是题目中说的不可以包含重复的三元组。

把符合条件的三元组放进vector中,然后再去重,这样是非常费时的,很容易超时,也是这道题目通过率如此之低的根源所在。

去重的过程不好处理,有很多小细节,如果在面试中很难想到位。

时间复杂度可以做到$O(n^2)$,但还是比较费时的,因为不好做剪枝操作

解题思路:

  • 排序 + 双指针
java
public List<List<Integer>> threeSum(int[] nums) {// 总时间复杂度:O(n^2)
    List<List<Integer>> ans = new ArrayList<>();
    if (nums == null || nums.length <= 2) return ans;

    Arrays.sort(nums); // O(nlogn)

    for (int i = 0; i < nums.length - 2; i++) { // O(n^2)
        if (nums[i] > 0) break; // 第一个数大于 0,后面的数都比它大,肯定不成立了
        if (i > 0 && nums[i] == nums[i - 1]) continue; // 去掉重复情况
        int target = -nums[i];
        int left = i + 1, right = nums.length - 1;
        while (left < right) {
            if (nums[left] + nums[right] == target) {
                ans.add(new ArrayList<>(Arrays.asList(nums[i], nums[left], nums[right])));
                
                // 现在要增加 left,减小 right,但是不能重复,比如: [-2, -1, -1, -1, 3, 3, 3], i = 0, left = 1, right = 6, [-2, -1, 3] 的答案加入后,需要排除重复的 -1 和 3
                left++; right--; // 首先无论如何先要进行加减操作
                while (left < right && nums[left] == nums[left - 1]) left++;
                while (left < right && nums[right] == nums[right + 1]) right--;
            } else if (nums[left] + nums[right] < target) {
                left++;
            } else {  // nums[left] + nums[right] > target
                right--;
            }
        }
    }
    return ans;
}

四数之和

思路同三数之和:排序 + 双指针 + 两层 for 循环
不同之处:

  1. 对比三数之和,不需要开始 for 循环时的剪枝操作 if (nums[i] > target) break ,因为四数之和的 target 可以是负数,四个数的首位值大于 0 只是针对 target 为非负数的情况(负数是需要末尾值小于 target)
    2.剪枝操作:
    if ((long) nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) {
    break;
    }
    if ((long) nums[i] + nums[j] + nums[length - 2] + nums[length - 1] < target) {
    continue;
    }
java
public List<List<Integer>> fourSum(int[] nums, int target) {
    List<List<Integer>> result = new ArrayList<List<Integer>>();
    if (nums == null || nums.length < 4) {
        return result;
    }
    Arrays.sort(nums);
    int length = nums.length;
    // -2 -1 0 0 1 2 / 0
    for (int i = 0; i < length - 3; ++i) {
        // 对比三数之和,不用判断 nums[i] > target
        // if (nums[i] > target) break;
        if (i > 0 && nums[i] == nums[i - 1]) continue;
        if ((long) nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) {
            break;
        }
        if ((long) nums[i] + nums[length - 3] + nums[length - 2] + nums[length - 1] < target) {
            continue;
        }
        for (int j = i + 1; j < length - 2; ++j) {
            if ( j > i + 1 && nums[j] == nums[j - 1]) continue;
            if ((long) nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) {
                break;
            }
            if ((long) nums[i] + nums[j] + nums[length - 2] + nums[length - 1] < target) {
                continue;
            }
            int temp = nums[i] + nums[j];
            int tempTarget = target - temp;
            int left = j + 1, right = length - 1;
            while (left < right) {
                if (nums[left] + nums[right] == tempTarget) {
                    result.add(new ArrayList<>(Arrays.asList(nums[i], nums[j], 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 (nums[left] + nums[right] < tempTarget) {
                    left++;
                } else {
                    right--;
                }
            }
        }
    }
    return result;
}

删除

排序好的数组

快慢双指针

未排序

快慢双指针(分别从起始和末尾考虑, head 和 tail)

动态规划

带备忘录的递归结构

核心:拆分子问题,记住过往,减少重复计算

  1. 穷举分析
  2. 确定边界
  3. 找规律,确定最优子结构
  4. 写出状态方程
  5. 代码实现
  • 最优子结构
  • 状态转移方程
  • 边界
  • 重叠子问题
  • 背包问题
  • 最小编辑距离
ad-quote
title:参考文章
[看一遍就理解:动态规划详解 - 云+社区 - 腾讯云](https://cloud.tencent.com/developer/article/1817113)

二叉树

生成二叉树

数组生成二叉树——遍历

java
import org.junit.Test;
 
import java.util.ArrayList;
import java.util.List;
 
/**
 * 二叉树的生成
 *
 * @author DangerShi
 */
public class TreeBuilder {
 
    class TreeNode {
        Object data;
        TreeNode left;
        TreeNode right;
 
        public TreeNode() {
        }
 
        public TreeNode(Object data, TreeNode left, TreeNode right) {
            this.data = data;
            this.left = left;
            this.right = right;
        }
    }
 
    public TreeNode arrayToBTree(Object[] arrs) {
        if (arrs == null || arrs.length == 0) {
            return new TreeNode();
        }
 
        List<TreeNode> nodes = new ArrayList<>(arrs.length);
        for (Object obj : arrs) {
            TreeNode treeNode = new TreeNode();
            treeNode.data = obj;
            nodes.add(treeNode);
        }
 
        for (int i = 0; i < arrs.length/2 - 1; i++) {
            TreeNode node = nodes.get(i);
            node.left = nodes.get(i*2 + 1);
            node.right = nodes.get(i*2 + 2);
        }
        // 只有当总节点数是奇数时,最后一个父节点才有右子节点
        int lastPNodeIndex = arrs.length/2 - 1;
        TreeNode lastPNode = nodes.get(lastPNodeIndex);
        lastPNode.left = nodes.get(lastPNodeIndex*2 + 1);
        if (arrs.length%2 != 0) {
            lastPNode.right = nodes.get(lastPNodeIndex*2 + 2);
        }
 
        return nodes.get(0);
    }
 
    @Test
    public void test() {
        /**
         *          1
         *      2       3
         *    4   5   6   7
         */
        TreeNode treeNode = arrayToBTree(new Object[]{1,2,3,4,5,6,7});
    }
 
}

数组生成二叉树——递归(数组是升序的,生成方式为中序遍历)

java
class Solution {

 public TreeNode sortedArrayToBST(int[] nums) {

 return helper(nums, 0, nums.length - 1);

 }

 public TreeNode helper(int[] nums, int left, int right){

 if(left > right){

 return null;

 }

 int mid = (left + right) / 2;

 TreeNode root = new TreeNode(nums[mid]);

 root.left = helper(nums, left, mid - 1);

 root.right = helper(nums, mid + 1, right);

 return root;

 }

}

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;

 }

}

boyer-moore 算法

#boyer-moore
背景

java
public int majorityElement(int[] nums) {

 int count = 0;

 int temp = 0;

 for(int i = 0; i < nums.length; i++) {

 if(count == 0){

 temp = nums[i];

 }

 count += (temp == nums[i]) ? 1 : -1;

 }

 return temp;

 }

贡献者

The avatar of contributor named as jiechen jiechen

页面历史

撰写