极客时间《算法面试40讲》笔记
反转链表和判断链表是否有环
LC24_两两交换链表中的结点
public class Solution1 {
/**
* LC24:两两交换链表中的结点
* 示例:1,2,3,4 反转成 2,1,4,3
* 迭代
*/
public ListNode swapPairs(ListNode head) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode temp = dummy;
while (temp.next != null && temp.next.next != null) {
ListNode node1 = temp.next;
ListNode node2 = temp.next.next;
temp.next = node2;
node1.next = node2.next;
node2.next = node1;
temp = node1;
}
return dummy.next;
}
}
LC25_k个一组反转链表
public class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
if (head == null || head.next == null || k == 1) {
return head;
}
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy, cur = head, next;
int len = 0;
while (head != null) {
len++;
head = head.next;
}
// 整个链表有len/k子块需要翻转
for (int i = 0; i < len / k; i++) {
// 单个子系列中,需要翻转k-1次
for (int j = 0; j < k - 1; j++) {
// 这里翻转的过程,需要自己画图模拟头插入
// 初始化:pre指向cur前一个,cur从需要反转的头结点开始,next记录cur后继结点
//(1)记录cur后继结点
next = cur.next;
//(2)中间节点cur指向后继的后继
cur.next = next.next;
//(3)next后继指向子序列的头结点,不是指向cur
next.next = pre.next;
//(4)哑结点后继执行next
pre.next = next;
}
// 一个字块更新完,移动pre和cur
pre = cur;
cur = cur.next;
}
return dummy.next;
}
}
LC141_环形链表
public class Solution {
/**
* 判断链表是否有环
*/
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null || head.next.next == null) {
return false;
}
ListNode fast = head.next.next;
ListNode slow = head.next;
while (fast != slow) {
if (fast.next == null || fast.next.next == null) {
return false;
}
fast = fast.next.next;
slow = slow.next;
}
return true;
}
}
LC142_环形链表II
public class Solution {
/**
* 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回null
*/
public ListNode detectCycle(ListNode head) {
if (head == null || head.next == null || head.next.next == null) {
return null;
}
ListNode fast = head.next.next;
ListNode slow = head.next;
// 快指针走2步,慢指针走1步
while (fast != slow) {
if (fast.next == null || fast.next.next == null) {
return null;
}
fast = fast.next.next;
slow = slow.next;
}
// 以上代码=判断链表是否有环,快指针一旦遇到慢指针,说明肯定有环,让快指针重新指向头结点
fast = head;
// 快指针走1步,慢指针走1步
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
// 快慢指针一定会在第一个入环结点相遇,证明找wolai笔记
return fast;
}
}
LC206_反转链表
public class Solution {
/**
* LC206:反转单链表
* 迭代
*/
public ListNode reverseList(ListNode head) {
ListNode cur = head;
ListNode pre = null;
while (cur != null) {
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
}
判断字符串是否合法
LC20_有效的括号
public class Solution {
/**
* 有效的括号
*/
public boolean isValid(String s) {
if (s == null || s.length() == 0) {
return false;
}
Map<Character, Character> map = new HashMap<>(3);
map.put('(', ')');
map.put('[', ']');
map.put('{', '}');
LinkedList<Character> stack = new LinkedList<>();
for (char c : s.toCharArray()) {
if (stack.isEmpty() || map.containsKey(c)) {
stack.push(c);
} else {
if (map.get(stack.peek()) != c) {
return false;
}
stack.pop();
}
}
return stack.isEmpty();
}
public static void main(String[] args) {
Solution solution = new Solution();
String s = "()";
System.out.println(solution.isValid(s));
}
}
栈和队列相互实现
LC225_队列实现栈
public class MyStack {
/**
* 队列实现栈:可以用一个队列,模拟一个栈
*/
private Queue<Integer> queue;
public MyStack() {
queue = new LinkedList<>();
}
public void push(int x) {
// 保证后进的元素,维持在队列头部,便于出去
// 所以先记录之前队列的长度
int size = queue.size();
queue.add(x);
// 之前长度内的元素重新进入队列,让x保持在队头
for (int i = 0; i < size; i++) {
queue.add(queue.poll());
}
}
public int pop() {
return queue.poll();
}
public int top() {
return queue.peek();
}
public boolean empty() {
return queue.isEmpty();
}
}
LC232_用栈实现队列
public class MyQueue {
/**
* 用栈实现队列,一定是使用两个栈:stack1正常压入,stack2维持stack1的栈底到stack2的栈顶
*/
private LinkedList<Integer> stack1;
private LinkedList<Integer> stack2;
public MyQueue() {
stack1 = new LinkedList<>();
stack2 = new LinkedList<>();
}
public void push(int x) {
stack1.push(x);
}
public int pop() {
if (empty()) {
return -1;
}
// 栈1非空,栈2为空,往栈2压入数据
if (stack2.isEmpty()) {
pushStack2();
}
return stack2.pop();
}
public int peek() {
if (empty()) {
return -1;
}
// 栈1非空,栈2为空,往栈2压入数据
if (stack2.isEmpty()) {
pushStack2();
}
return stack2.peek();
}
public boolean empty() {
return stack1.isEmpty() && stack2.isEmpty();
}
/**
* 摊还时间复杂度
*/
private void pushStack2() {
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
}
LC703_数据流中第k大元素
public class KthLargest {
/**
* 优先级队列
*/
private PriorityQueue<Integer> minHeap;
private int k;
public KthLargest(int k, int[] nums) {
minHeap = new PriorityQueue<>();
this.k = k;
for (int num : nums) {
// 调用add操作
add(num);
}
}
public int add(int val) {
if (minHeap.size() < k) {
minHeap.offer(val);
} else if (minHeap.peek() < val) {
// 数据流中第k大元素一定是排序后,最后k个元素中最小的
minHeap.poll();
minHeap.offer(val);
}
return minHeap.peek();
}
public static void main(String[] args) {
int k = 3;
int[] nums = {4, 5, 8, 2};
KthLargest kthLargest = new KthLargest(k, nums);
System.out.println(kthLargest.add(3));
}
}
LC239_滑动窗口最大值
public class Solution {
/**
* 滑动窗口最大值
* 输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
* 输出:[3,3,5,5,6,7]
* 解释:
* 滑动窗口的位置 最大值
* --------------- -----
* [1 3 -1] -3 5 3 6 7 3
* 1 [3 -1 -3] 5 3 6 7 3
* 1 3 [-1 -3 5] 3 6 7 5
* 1 3 -1 [-3 5 3] 6 7 5
* 1 3 -1 -3 [5 3 6] 7 6
* 1 3 -1 -3 5 [3 6 7] 7
*/
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || nums.length < k || k < 1) {
return new int[]{};
}
// 双端队列队头存区间最大值下标
Deque<Integer> maxIndexDeque = new LinkedList<>();
int[] res = new int[nums.length - k + 1];
int index = 0;
for (int i = 0; i < nums.length; i++) {
// 双端队列,队头保证存窗口内最大值下标
while (!maxIndexDeque.isEmpty() && nums[maxIndexDeque.peekLast()] <= nums[i]) {
maxIndexDeque.pollLast();
}
// 存下标
maxIndexDeque.addLast(i);
// 数字下标与队列头差值>=窗口长度,队头移出队列
if (i - maxIndexDeque.peekFirst() >= k) {
maxIndexDeque.pollFirst();
}
// 数字下标>=窗口长度下标,就要记录队头元素
if (i >= k - 1) {
res[index++] = nums[maxIndexDeque.peekFirst()];
}
}
return res;
}
public static void main(String[] args) {
int[] nums = {1, 3, -1, -3, 5, 3, 6, 7};
int k = 3;
Solution solution = new Solution();
int[] res = solution.maxSlidingWindow(nums, k);
System.out.println(Arrays.toString(res));
}
@Test
public void deQueTest() {
Deque<Integer> maxIndexDeque = new LinkedList<>();
maxIndexDeque.addLast(1);
maxIndexDeque.addLast(2);
maxIndexDeque.addLast(3);
System.out.println(maxIndexDeque.peekLast());
}
}
有效的字母异位词
LC242_有效的字母异位词
public class Solution {
/**
* 有效的字母异位词
* 输入: s = "anagram", t = "nagaram"
* 输出: true
*/
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
int[] map = new int[256];
for (char c : s.toCharArray()) {
if (map[c] != 0) {
map[c]++;
} else {
map[c] = 1;
}
}
for (char c : t.toCharArray()) {
if (map[c] != 0) {
map[c]--;
if (map[c] < 0) {
return false;
}
} else {
return false;
}
}
return true;
}
public static void main(String[] args) {
Solution solution = new Solution();
String s = "ab";
String t = "a";
System.out.println(solution.isAnagram(s, t));
}
}
LC1_两数之和
public class Solution {
/**
* 两数之和
*/
public int[] twoSum(int[] nums, int target) {
if (nums == null || nums.length < 2) {
return new int[]{-1, -1};
}
// 存数组值,对应下标
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if (!map.containsKey(target - nums[i])) {
map.put(nums[i], i);
} else {
return new int[]{map.get(target - nums[i]), i};
}
}
return new int[]{-1, -1};
}
public static void main(String[] args) {
Solution solution = new Solution();
int[] nums = {2, 7, 11, 15};
int target = 9;
System.out.println(Arrays.toString(solution.twoSum(nums, target)));
}
}
LC15_三数之和
public class Solution {
/**
* 三数之和
* 输入:nums = [-1,0,1,2,-1,-4]
* 输出:[[-1,-1,2],[-1,0,1]]
*/
public List<List<Integer>> threeSum(int[] nums) {
// sort + find:将数组排序后查找
if (nums == null || nums.length < 3) {
return new ArrayList<>();
}
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);
int n = nums.length;
// 三数之和,需要两重循环
// 第一重循环:固定数nums[i]
for (int i = 0; i < n - 2; i++) {
// 三数之和=0,如果排序后第一个数>0,必不存在
if (nums[i] > 0) {
return res;
}
// 排序后,i之前相同的数无需重复判断,需要去重
// 注意:不能是i之后的相同的数去重,因为i之后的数可以相同判断三数之和
// [-4,-1,-1,0,1,2]
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
// 优化1
if ((long) nums[i] + nums[i + 1] + nums[i + 2] > 0) {
break;
}
// 优化2
if ((long) nums[i] + nums[n - 2] + nums[n - 1] < 0) {
continue;
}
int left = i + 1;
int right = n - 1;
// 第二重循环:找两个数
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum < 0) {
left++;
} else if (sum > 0) {
right--;
} else {
res.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 去重
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
left++;
right--;
}
}
}
return res;
}
public static void main(String[] args) {
Solution solution = new Solution();
int[] nums = {-1, 0, 1, 2, -1, -4};
System.out.println(solution.threeSum(nums));
}
}
LC18_四数之和
public class Solution {
/**
* 四数之和
* 输入:nums = [1,0,-1,0,-2,2], target = 0
* 输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
*/
public List<List<Integer>> fourSum(int[] nums, int target) {
if (nums == null || nums.length < 4) {
return new ArrayList<>();
}
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);
int n = nums.length;
// 三重循环,第一重:固定数nums[i]
for (int i = 0; i < n - 3; i++) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
// 剪枝:当前数+前3个数>target,退出本轮循环
if ((long) nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) {
break;
}
// 剪枝:当前数+后3个数
if ((long) nums[i] + nums[n - 3] + nums[n - 2] + nums[n - 1] < target) {
continue;
}
// 第二重:固定数nums[j]
for (int j = i + 1; j < n - 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[n - 2] + nums[n - 1] < target) {
continue;
}
int left = j + 1;
int right = n - 1;
// 第三重循环:找两个数
while (left < right) {
int sum = nums[i] + nums[j] + nums[left] + nums[right];
if (sum < target) {
left++;
} else if (sum > target) {
right--;
} else {
res.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
// 去重
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
left++;
right--;
}
}
}
}
return res;
}
public static void main(String[] args) {
Solution solution = new Solution();
int[] nums = {2, 2, 2, 2};
int target = 8;
System.out.println(solution.fourSum(nums, target));
}
}
验证二叉搜索树
public class Solution {
/**
* 验证二叉搜索树
*/
public boolean isValidBST(TreeNode root) {
if (root == null) {
return false;
}
List<Integer> list = new ArrayList<>();
inorder(root, list);
// 判断list元素是否是严格递增的
for (int i = 1; i < list.size(); i++) {
int pre = list.get(i - 1);
int next = list.get(i);
// 严格递增,有等于也不是严格递增
if (pre >= next) {
return false;
}
}
return true;
}
private void inorder(TreeNode root, List<Integer> list) {
if (root == null) {
return;
}
inorder(root.left, list);
list.add(root.val);
inorder(root.right, list);
}
}
最近公共祖先问题
LC235_二叉搜索树的最近公共祖先
public class Solution {
/**
* 二叉搜索树的最近公共祖先
*/
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// 不改变原树结构
TreeNode cur = root;
while (true) {
if (cur.val < p.val && cur.val < q.val) {
cur = cur.right;
} else if (cur.val > p.val && cur.val > q.val) {
cur = cur.left;
} else {
break;
}
}
return cur;
}
}
LC236_二叉树的最近公共祖先
public class Solution {
/**
* 二叉树的最近公共祖先
*/
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root == p || root == q) {
return root;
}
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
// 成功的base case:最近公共祖先,两者都不为空
if (left != null && right != null) {
return root;
}
return left != null ? left : right;
}
}
public class NotRecursiveTraverse {
// 先序非递归遍历(力扣144)
public static void preTraverse(TreeNode head) {
if (head == null) {
return;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(head);
while (!stack.isEmpty()) {
head = stack.pop();
System.out.print(head.value + " ");
// 先序:先压右孩子,再压左孩子
if (head.right != null) {
stack.push(head.right);
}
if (head.left != null) {
stack.push(head.left);
}
}
System.out.println();
}
// 中序非递归遍历(力扣94)
public static void inTraverse(TreeNode head) {
if (head == null) {
return;
}
Stack<TreeNode> stack = new Stack<>();
while (!stack.isEmpty() || head != null) {
// 中序非递归遍历先把所有左子树入栈
if (head != null) {
stack.push(head);
head = head.left;
} else {
// 左子树到null就出栈,操作+入右子树
head = stack.pop();
System.out.println(head.value + " ");
head = head.right;
}
}
System.out.println();
}
// 后序非递归遍历(力扣145)
public static void posTraverse(TreeNode head) {
if (head == null) {
return;
}
Stack<TreeNode> stack = new Stack<>();// 收集栈:存头右左
Stack<TreeNode> temp = new Stack<>();// 辅助栈:存收集栈每次的出栈
stack.push(head);
while (!stack.isEmpty()) {
// 辅助栈每次收集收集栈的出栈元素
head = stack.pop();
temp.push(head);
// 收集栈每次存左右,出的时候就变成右左了
if (head.left != null) {
stack.push(head.left);
}
if (head.right != null) {
stack.push(head.right);
}
}
// 辅助栈依次出栈就是后续遍历
while (!temp.isEmpty()) {
System.out.println(temp.pop().value + "");
}
System.out.println();
}
}
public class WidthTraverse {
// 二叉树的宽度遍历 = 层次遍历(力扣102)
public List<List<Integer>> levelOrder(TreeNode root) {
if (root == null) {
return new ArrayList<>();
}
Queue<TreeNode> queue = new LinkedList<>();
List<List<Integer>> res = new ArrayList<>();
queue.add(root);
while (!queue.isEmpty()) {
// 辅助数组在每次循环内生成,保证每一层都是新的List
List<Integer> temp = new ArrayList<>();
// 从利用queue的个数来遍历,让每一层的queue都遍历完
for (int i = queue.size(); i > 0; i--) {
TreeNode node = queue.poll();
temp.add(node.val);
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
res.add(temp);
}
return res;
}
}
Pow
LC50_pow
public class Solution {
/**
* pow(x,n)
* 输入:x = 2.00000, n = 10
* 输出:1024.00000
* 方法:快速幂法
*/
public double myPow(double x, int n) {
if (n == 0) {
return 1.0;
}
if (n == 1) {
return x;
}
long b = n;
if (b < 0) {
x = 1 / x;
b = -b;
}
double res = 1.0;
while (b > 0) {
if ((b & 1) == 1) {
res *= x;
}
x *= x;
b >>= 1;
}
return res;
}
public static void main(String[] args) {
Solution solution = new Solution();
double x = 2.0;
int n = 10;
System.out.println(solution.myPow(x, n));
}
}
求众数
LC169_多数元素
public class Solution1 {
/**
* 多数元素
* 输入:[2,2,1,1,1,2,2]
* 输出:2
* 多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
* 条件给定的数组总是存在多数元素。
*/
public int majorityElement(int[] nums) {
// 摩尔投票法
int x = 0, votes = 0;
for (int num : nums) {
if (votes == 0) {
x = num;
}
votes += (x == num) ? 1 : -1;
}
int count = 0;
for (int num : nums) {
if (x == num) {
count++;
}
}
if (count > nums.length / 2) {
return x;
}
throw new RuntimeException("不存在多数元素");
}
}
买卖股票最佳时机
LC121_买卖股票最佳时机I
public class Solution {
/**
* LC122_买卖股票最佳时机I
* 输入:[7,1,5,3,6,4]
* 输出:5
* 解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
* 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
*/
public int maxProfit(int[] prices) {
// 找出买卖一只股票获得最大利润
int min = prices[0];
int profit = 0;
for (int i = 1; i < prices.length; i++) {
min = Math.min(min, prices[i]);
profit = Math.max(profit, prices[i] - min);
}
return profit;
}
}
LC122_买卖股票最佳时机II
public class Solution {
/**
* LC122_买卖股票最佳时机II
* 输入: prices = [7,1,5,3,6,4]
* 输出: 7
* 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
* 在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
*/
public int maxProfit(int[] prices) {
if (prices.length < 2) {
return 0;
}
int profit = 0;
// 贪心:只要后一天价格>前一天价格,就前一天买入,后一天卖出
for (int i = 0; i < prices.length - 1; i++) {
if (prices[i] < prices[i + 1]) {
profit += prices[i + 1] - prices[i];
}
}
return profit;
}
public static void main(String[] args) {
Solution solution = new Solution();
int[] nums = {7, 1, 5, 3, 6, 4};
System.out.println(solution.maxProfit(nums));
}
}
public class Solution1 {
/**
* LC122_买卖股票最佳时机II
* 输入: prices = [7,1,5,3,6,4]
* 输出: 7
* 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
* 在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
*/
public int maxProfit(int[] prices) {
if (prices.length < 2) {
return 0;
}
// 动态规划:dp[i][j]表示第i天买还是不买股票
int[][] dp = new int[prices.length][2];
dp[0][0] = 0;
dp[0][1] = -prices[0];
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][1], dp[i - 1][0] - prices[i]);
}
return dp[prices.length - 1][0];
}
public static void main(String[] args) {
Solution1 solution = new Solution1();
int[] nums = {7, 1, 5, 3, 6, 4};
System.out.println(solution.maxProfit(nums));
}
}
二叉树层次遍历
public class Solution {
/**
* 二叉树层次遍历
* 输入:root = [3,9,20,null,null,15,7]
* 输出:[[3],[9,20],[15,7]]
*/
public List<List<Integer>> levelOrder(TreeNode root) {
if (root == null) {
return new ArrayList<>();
}
List<List<Integer>> res = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
// 每一次都新开辟temp,temp就无须新清空
List<Integer> temp = new ArrayList<>();
for (int i = queue.size(); i > 0; i--) {
TreeNode node = queue.poll();
temp.add(node.val);
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
res.add(temp);
}
return res;
}
public static void main(String[] args) {
Solution solution = new Solution();
TreeNode root = new TreeNode(3);
TreeNode node1 = new TreeNode(9);
TreeNode node2 = new TreeNode(20);
TreeNode node3 = new TreeNode(15);
TreeNode node4 = new TreeNode(7);
root.left = node1;
root.right = node2;
node2.left = node3;
node2.right = node4;
System.out.println(solution.levelOrder(root));
}
}
二叉树最大和最小深度
LC104_二叉树最大深度
public class Solution {
/**
* 二叉树最大深度
* 给定二叉树 [3,9,20,null,null,15,7]
* 返回最大深度3
*/
public int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
}
LC111_二叉树最小深度
public class Solution {
/**
* 二叉树最小深度
* 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
* 输入:root = [3,9,20,null,null,15,7]
* 输出:2
*/
public int minDepth(TreeNode root) {
if (root == null) {
return 0;
}
int left = minDepth(root.left);
int right = minDepth(root.right);
// 左右子树有一个为0
if (left == 0 || right == 0) {
return left + right + 1;
}
// 左右子树都不为0
return Math.min(left, right) + 1;
}
public static void main(String[] args) {
TreeNode root = new TreeNode(3);
TreeNode node1 = new TreeNode(9);
TreeNode node2 = new TreeNode(20);
TreeNode node3 = new TreeNode(15);
TreeNode node4 = new TreeNode(7);
root.left = node1;
root.right = node2;
node2.left = node3;
node2.right = node4;
Solution solution = new Solution();
System.out.println(solution.minDepth(root));
}
}
生成有效括号组合
LC22_括号生成
public class Solution {
/**
* 括号生成
* 生成所有可能且有效的括号组合
* 输入:n = 3
* 输出:["((()))","(()())","(())()","()(())","()()()"]
*/
public List<String> generateParenthesis(int n) {
List<String> res = new ArrayList<>();
// n对括号,会生成2n长的字符串,使用dfs
recursion(n, n, "", res);
return res;
}
private void recursion(int leftNeed, int rightNeed, String subList, List<String> res) {
if (leftNeed == 0 && rightNeed == 0) {
res.add(subList);
return;
}
// 还有需要的左括号,就添加左括号
if (leftNeed > 0) {
recursion(leftNeed - 1, rightNeed, subList + "(", res);
}
// 还需要左括号<还需要的右括号 = 已经生成的左括号>已经生成的右括号,还需要补充右括号
if (leftNeed < rightNeed) {
recursion(leftNeed, rightNeed - 1, subList + ")", res);
}
}
}
N皇后问题
LC51_N皇后
public class Solution {
List<List<String>> res = new ArrayList<>();
/**
* N皇后
* 输入:n = 4
* 输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
* 解释:如上图所示,4 皇后问题存在两个不同的解法。
*/
public List<List<String>> solveNQueens(int n) {
// 初始化cs二维数组,根据题目条件初始化放入.
char[][] cs = new char[n][n];
for (char[] c : cs) {
Arrays.fill(c, '.');
}
backTrack(cs, 0, n);
return res;
}
public void backTrack(char[][] cs, int i, int n) {
// 终止条件:row越过n-1,说明这是一种正确的放法
if (i == n) {
List<String> list = new ArrayList<>();
for (char[] c : cs) {
// char[]直接转成String,加进list中
list.add(String.copyValueOf(c));
}
res.add(list);
return;
}
for (int j = 0; j < n; ++j) {
// 到达一行,遍历该行的每一列是否与之前的皇后是否产生攻击
if (isValid(cs, i, j, n)) {
// 将有效当期位设为Q
cs[i][j] = 'Q';
// 递归下一行
backTrack(cs, i + 1, n);
// 回溯,当前有效位还原,循环开启下一行
cs[i][j] = '.';
}
}
}
public boolean isValid(char[][] cs, int x, int y, int n) {
// 行不用检查,因为是逐行放入cs中
// 检查列
for (int i = 0; i < n; ++i) {
if (cs[i][y] == 'Q') {
return false;
}
}
// 检查45°斜线
for (int i = x - 1, j = y - 1; i >= 0 && j >= 0; i--, j--) {
if (cs[i][j] == 'Q') {
return false;
}
}
// 检查135°反斜线
for (int i = x - 1, j = y + 1; i >= 0 && j <= n - 1; i--, j++) {
if (cs[i][j] == 'Q') {
return false;
}
}
return true;
}
}
LC52_N皇后II
public class Solution {
private int count = 0;
/**
* N皇后II
* 输入:n = 4
* 输出:2
*/
public int totalNQueens(int n) {
// 初始化cs二维数组,根据题目条件初始化放入.
char[][] cs = new char[n][n];
for (char[] c : cs) {
Arrays.fill(c, '.');
}
backTrack(cs, 0, n);
return count;
}
public void backTrack(char[][] cs, int i, int n) {
// 终止条件:row越过n-1,说明这是一种正确的放法
if (i == n) {
count++;
return;
}
for (int j = 0; j < n; ++j) {
// 到达一行,遍历该行的每一列是否与之前的皇后是否产生攻击
if (isValid(cs, i, j, n)) {
// 将有效当期位设为Q
cs[i][j] = 'Q';
// 递归下一行
backTrack(cs, i + 1, n);
// 回溯,当前有效位还原,循环开启下一行
cs[i][j] = '.';
}
}
}
public boolean isValid(char[][] cs, int row, int col, int n) {
// 行不用检查,因为是逐行放入cs中
// 检查列
for (int i = 0; i < n; ++i) {
if (cs[i][col] == 'Q') {
return false;
}
}
// 检查45°斜线
for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
if (cs[i][j] == 'Q') {
return false;
}
}
// 检查135°反斜线
for (int i = row - 1, j = col + 1; i >= 0 && j <= n - 1; i--, j++) {
if (cs[i][j] == 'Q') {
return false;
}
}
return true;
}
}
数独问题
LC36_有效的数独
public class Solution {
/**
* 有效的数独
* 输入:board =
* [["5","3",".",".","7",".",".",".","."]
* ,["6",".",".","1","9","5",".",".","."]
* ,[".","9","8",".",".",".",".","6","."]
* ,["8",".",".",".","6",".",".",".","3"]
* ,["4",".",".","8",".","3",".",".","1"]
* ,["7",".",".",".","2",".",".",".","6"]
* ,[".","6",".",".",".",".","2","8","."]
* ,[".",".",".","4","1","9",".",".","5"]
* ,[".",".",".",".","8",".",".","7","9"]]
* 输出:true
* 注意:空白格用'.'表示
*/
public boolean isValidSudoku(char[][] board) {
if (board == null || board.length == 0) {
return false;
}
return dfs(board);
}
private boolean dfs(char[][] board) {
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (board[i][j] != '.') {
if (!isValid(board, i, j, board[i][j])) {
return false;
}
}
}
}
return true;
}
/**
* board[row][col]能否放入c
*/
private boolean isValid(char[][] board, int row, int col, char c) {
for (int i = 0; i < 9; i++) {
// 检查行是否有c
if (board[i][col] != '.' && board[i][col] == c) {
// 原本的位置不用检查
if (i == row) {
continue;
}
return false;
}
// 检查列是否有c
if (board[row][i] != '.' && board[row][i] == c) {
if (i == col) {
continue;
}
return false;
}
// 检查3*3的方格里是否有c
// i=0,1,2都属于第0个方格,所以用/3
int x = 3 * (row / 3) + i / 3;
// i=3,4,5都要回到0,1,2的位置,所以用%3
int y = 3 * (col / 3) + i % 3;
if (board[x][y] != '.' && board[x][y] == c) {
if (x == row && y == col) {
continue;
}
return false;
}
}
return true;
}
public static void main(String[] args) {
Solution solution = new Solution();
char[][] cs = {
{'5', '3', '.', '.', '7', '.', '.', '.', '.'},
{'6', '.', '.', '1', '9', '5', '.', '.', '.'},
{'.', '9', '8', '.', '.', '.', '.', '6', '.'},
{'8', '.', '.', '.', '6', '.', '.', '.', '3'},
{'4', '.', '.', '8', '.', '3', '.', '.', '1'},
{'7', '.', '.', '.', '2', '.', '.', '.', '6'},
{'.', '6', '.', '.', '.', '.', '2', '8', '.'},
{'.', '.', '.', '4', '1', '9', '.', '.', '5'},
{'.', '.', '.', '.', '8', '.', '.', '7', '9'}};
System.out.println(solution.isValidSudoku(cs));
}
}
LC37_解数独
public class Solution {
/**
* 解数独
*/
public void solveSudoku(char[][] board) {
if (board == null || board.length == 0) {
return;
}
dfs(board);
}
private boolean dfs(char[][] board) {
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
// 空白位置用'.'代替
if (board[i][j] == '.') {
// 每一个空白位置用字符1-9去尝试
for (char c = '1'; c <= '9'; c++) {
// i,j位置能否放入字符c
if (isValid(board, i, j, c)) {
board[i][j] = c;
// 递归下一个空白位置
if (dfs(board)) {
return true;
} else {
board[i][j] = '.';
}
}
}
return false;
}
}
}
return true;
}
/**
* board[row][col]能否放入c
*/
private boolean isValid(char[][] board, int row, int col, char c) {
for (int i = 0; i < 9; i++) {
// 检查行是否有c
if (board[i][col] != '.' && board[i][col] == c) {
return false;
}
// 检查列是否有c
if (board[row][i] != '.' && board[row][i] == c) {
return false;
}
// 检查3*3的方格里是否有c
int x = 3 * (row / 3) + i / 3;
int y = 3 * (col / 3) + i % 3;
if (board[x][y] != '.' && board[x][y] == c) {
return false;
}
}
return true;
}
public static void main(String[] args) {
Solution solution = new Solution();
char[][] cs = {
{'5', '3', '.', '.', '7', '.', '.', '.', '.'},
{'6', '.', '.', '1', '9', '5', '.', '.', '.'},
{'.', '9', '8', '.', '.', '.', '.', '6', '.'},
{'8', '.', '.', '.', '6', '.', '.', '.', '3'},
{'4', '.', '.', '8', '.', '3', '.', '.', '1'},
{'7', '.', '.', '.', '2', '.', '.', '.', '6'},
{'.', '6', '.', '.', '.', '.', '2', '8', '.'},
{'.', '.', '.', '4', '1', '9', '.', '.', '5'},
{'.', '.', '.', '.', '8', '.', '.', '7', '9'}};
solution.solveSudoku(cs);
}
}
求平方根
LC69_平方根
public class Solution {
/**
* 求x平方根
*/
public int mySqrt(int x) {
if (x == 0 || x == 1) {
return x;
}
int left = 1, right = x;
int res = 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (mid < x / mid) {
left = mid + 1;
// 左边界移动,res等于mid
res = mid;
} else if (mid > x / mid) {
right = mid - 1;
} else {
return mid;
}
}
return res;
}
}
补充,平方根精确到小数点n位
public class CalSqrt {
/**
* 精度:精确到小数点后6位
*/
private final double ACCURACY = 0.000001;
/**
* 附加:求一个数x的平方根,精确到小数点后6位
*/
public float calSqrt(int x) {
// 所有类型都为浮点型
float left = 0;
float right = x;
while (Math.abs(right - left) >= ACCURACY) {
float mid = left + (right - left) / 2;
float mid2 = mid * mid;
if (mid2 - x > ACCURACY) {
right = mid;
} else if (x - mid2 > ACCURACY) {
left = mid;
} else {
return mid;
}
}
return -1;
}
}
实现字典树
LC208_实现Trie
public class Trie {
static class TrieNode {
public boolean isWordEnd;
public TrieNode[] children;
public TrieNode() {
// 26个字母
children = new TrieNode[26];
}
}
private TrieNode root;
/**
* 实现一个前缀树
*/
public Trie() {
root = new TrieNode();
}
public void insert(String word) {
TrieNode cur = root;
for (char c : word.toCharArray()) {
if (cur.children[c - 'a'] == null) {
cur.children[c - 'a'] = new TrieNode();
}
cur = cur.children[c - 'a'];
}
cur.isWordEnd = true;
}
public boolean search(String word) {
TrieNode cur = root;
for (char c : word.toCharArray()) {
if (cur.children[c - 'a'] != null) {
cur = cur.children[c - 'a'];
} else {
return false;
}
}
return cur.isWordEnd;
}
public boolean startsWith(String prefix) {
TrieNode cur = root;
for (char c : prefix.toCharArray()) {
if (cur.children[c - 'a'] != null) {
cur = cur.children[c - 'a'];
} else {
return false;
}
}
return true;
}
}
二维数组中单词的搜索
LC79_单词搜索
public class Solution {
/**
* 单词搜索
* 输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
* 输出:true
*/
public boolean exist(char[][] board, String word) {
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (dfs(board, i, j, word, 0)) {
return true;
}
}
}
return false;
}
private boolean dfs(char[][] board, int i, int j, String word, int index) {
if (i < 0 || i >= board.length || j < 0 || j >= board[0].length || word.charAt(index) != board[i][j]) {
return false;
}
if (index == word.length() - 1) {
return true;
}
// 剪枝:防止单词搜索回退已检查过的位置
board[i][j] = '*';
boolean res = dfs(board, i + 1, j, word, index + 1)
|| dfs(board, i - 1, j, word, index + 1)
|| dfs(board, i, j + 1, word, index + 1)
|| dfs(board, i, j - 1, word, index + 1);
// 还原
board[i][j] = word.charAt(index);
return res;
}
}
LC212_单词搜索II
public class Solution {
/**
* 单词搜索II
* 输入:board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]], words = ["oath","pea","eat","rain"]
* 输出:["eat","oath"]
*/
public List<String> findWords(char[][] board, String[] words) {
// 去重:add时会有重复
Set<String> set = new HashSet<>();
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
for (int k = 0; k < words.length; k++) {
if (dfs(board, i, j, words[k], 0)) {
set.add(words[k]);
}
}
}
}
// 结果集转成list
return new ArrayList<>(set);
}
/**
* 复用79_单词搜索的dfs
*/
private boolean dfs(char[][] board, int i, int j, String word, int index) {
if (i < 0 || i >= board.length || j < 0 || j >= board[0].length || word.charAt(index) != board[i][j]) {
return false;
}
if (index == word.length() - 1) {
return true;
}
// 剪枝:防止单词搜索回退已检查过的位置
board[i][j] = '*';
boolean res = dfs(board, i + 1, j, word, index + 1)
|| dfs(board, i - 1, j, word, index + 1)
|| dfs(board, i, j + 1, word, index + 1)
|| dfs(board, i, j - 1, word, index + 1);
// 还原
board[i][j] = word.charAt(index);
return res;
}
}
public class Solution1 {
private Set<String> set = new HashSet<>();
/**
* 单词搜索II
* 输入:board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]], words = ["oath","pea","eat","rain"]
* 输出:["eat","oath"]
* 方法:使用Trie树
*/
public List<String> findWords(char[][] board, String[] words) {
// 将所有单词放入前缀树
Trie trie = new Trie();
for (String word : words) {
trie.insert(word);
}
int m = board.length;
int n = board[0].length;
boolean[][] visited = new boolean[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
dfs(board, i, j, visited, "", trie);
}
}
return new ArrayList<>(set);
}
private void dfs(char[][] board, int i, int j, boolean[][] visited, String str, Trie trie) {
if (i < 0 || i >= board.length || j < 0 || j >= board[0].length || visited[i][j]) {
return;
}
str += board[i][j];
if (!trie.startsWith(str)) {
return;
}
if (trie.search(str)) {
// 放入set中
set.add(str);
}
visited[i][j] = true;
dfs(board, i + 1, j, visited, str, trie);
dfs(board, i - 1, j, visited, str, trie);
dfs(board, i, j + 1, visited, str, trie);
dfs(board, i, j - 1, visited, str, trie);
visited[i][j] = false;
}
/**
* LC208_实现Trie数
*/
class Trie {
class TrieNode {
public boolean isWordEnd;
public TrieNode[] children;
public TrieNode() {
// 26个字母
children = new TrieNode[26];
}
}
private TrieNode root;
/**
* 实现一个前缀树
*/
public Trie() {
root = new TrieNode();
}
public void insert(String word) {
TrieNode cur = root;
for (char c : word.toCharArray()) {
if (cur.children[c - 'a'] == null) {
cur.children[c - 'a'] = new TrieNode();
}
cur = cur.children[c - 'a'];
}
cur.isWordEnd = true;
}
public boolean search(String word) {
TrieNode cur = root;
for (char c : word.toCharArray()) {
if (cur.children[c - 'a'] != null) {
cur = cur.children[c - 'a'];
} else {
return false;
}
}
return cur.isWordEnd;
}
public boolean startsWith(String prefix) {
TrieNode cur = root;
for (char c : prefix.toCharArray()) {
if (cur.children[c - 'a'] != null) {
cur = cur.children[c - 'a'];
} else {
return false;
}
}
return true;
}
}
public static void main(String[] args) {
Solution1 solution = new Solution1();
char[][] board = {
{'o', 'a', 'a', 'n'},
{'e', 't', 'a', 'e'},
{'i', 'h', 'k', 'r'},
{'i', 'f', 'l', 'v'}};
String[] words = {"oath", "pea", "eat", "rain"};
System.out.println(solution.findWords(board, words));
}
}
统计位的个数
LC191_位1的个数
public class Solution {
/**
* 位1的个数
* 输入:00000000000000000000000000001011
* 输出:3
*/
public int hammingWeight(int n) {
int count = 0;
while (n != 0) {
n &= n - 1;
count++;
}
return count;
}
}
2的幂次和比特位计数问题
LC231_2的幂
public class Solution {
/**
* 2的幂
* 输入:n = 1
* 输出:true
* 解释:2^0 = 1
*/
public boolean isPowerOfTwo(int n) {
// 若是2的幂,n & n-1必为0 且 n>0
return n > 0 && (n & n - 1) == 0;
}
}
LC338_比特位计数
public class Solution {
/**
* LC338_比特位计数
* 输入:n = 2
* 输出:[0,1,1]
* 解释:返回0到n中每个数中1的个数
* 0 --> 0
* 1 --> 1
* 2 --> 10
*/
public int[] countBits(int n) {
int[] res = new int[n + 1];
for (int i = 0; i <= n; i++) {
res[i] = hammingWeight(i);
}
return res;
}
/**
* 汉明码计数=位1的个数
*/
private int hammingWeight(int n) {
int count = 0;
while (n != 0) {
n &= n - 1;
count++;
}
return count;
}
}
爬楼梯
LC70_爬楼梯
public class Solution {
/**
* 爬楼梯
* 输入:n = 2
* 输出:2
* 输入:n = 3
* 输出:3
*/
public int climbStairs(int n) {
if (n < 0) {
return -1;
}
if (n <= 3) {
return n;
}
int a = 1;
int b = 2;
int sum;
for (int i = 0; i < n - 2; i++) {
sum = a + b;
a = b;
b = sum;
}
return b;
}
public static void main(String[] args) {
Solution solution = new Solution();
int n = 4;
System.out.println(solution.climbStairs(n));
}
}
三角形中的最小路径和
LC120_三角形最小路径和
public class Solution {
/**
* 三角形最小路径和
* 输入:triangle = [[2],[3,4],[6,5,7],[4,1,8,3]]
* 输出:11
* 解释:如下面简图所示:
* 2
* 3 4
* 6 5 7
* 4 1 8 3
* 自顶向下的最小路径和为11(2+3+5+1=11)
*/
public int minimumTotal(List<List<Integer>> triangle) {
if (triangle == null || triangle.size() == 0) {
return -1;
}
// 思考自顶向下,贪心是失败的,原因的最底部可能是大数导致结果失败
// 自底向上,从三角形最后一行往上找最小路径
int[] dp = new int[triangle.get(triangle.size() - 1).size()];
// dp初始化为三角形最底层
for (int i = 0; i < dp.length; i++) {
dp[i] = triangle.get(triangle.size() - 1).get(i);
}
// 从三角形倒数第二层开始动态规划
for (int i = triangle.size() - 2; i >= 0; i--) {
for (int j = 0; j < triangle.get(i).size(); j++) {
// dp=Math.min(下一层相邻)+当前节点值
dp[j] = Math.min(dp[j + 1], dp[j]) + triangle.get(i).get(j);
}
}
return dp[0];
}
public static void main(String[] args) {
List<List<Integer>> triangle = new ArrayList<>();
List<Integer> one = new ArrayList<>();
one.add(2);
List<Integer> two = new ArrayList<>();
two.add(3);
two.add(4);
List<Integer> three = new ArrayList<>();
three.add(6);
three.add(5);
three.add(7);
List<Integer> four = new ArrayList<>();
four.add(4);
four.add(1);
four.add(8);
four.add(3);
triangle.add(one);
triangle.add(two);
triangle.add(three);
triangle.add(four);
Solution solution = new Solution();
int res = solution.minimumTotal(triangle);
System.out.println(res);
}
}
乘积最大子序列
LC152_乘积最大子数组
public class Solution {
/**
* 乘积最大子数组
* 注意:子数组指数组的连续子序列
* 输入: nums = [2,3,-2,4]
* 输出: 6
*/
public int maxProduct(int[] nums) {
int max = Integer.MIN_VALUE;
// 由于存在负数,导致子数组乘积从最大变成最小
// 所以每一位数组元素都需要存当前位置的最大乘积、最小乘积
// iMax:表示0到i的最大乘积
int iMax = 1;
// iMin:表示0到i的最小乘积
int iMin = 1;
for (int i = 0; i < nums.length; i++) {
// 遇到负数,交换iMax、iMin
if (nums[i] < 0) {
int temp = iMax;
iMax = iMin;
iMin = temp;
}
iMax = Math.max(iMax * nums[i], nums[i]);
iMin = Math.min(iMin * nums[i], nums[i]);
max = Math.max(max, iMax);
}
return max;
}
public static void main(String[] args) {
Solution solution = new Solution();
int[] nums = {2, 3, -1, 4};
System.out.println(solution.maxProduct(nums));
}
}
买卖股票系列
LC188_买卖股票最佳时机IV
public class Solution {
/**
* 买卖股票的最佳时机IV
* 注意:'最多'可以完成k笔交易
* 输入:k = 2, prices = [2,4,1]
* 输出:2
* 解释:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。
*/
public int maxProfit(int k, int[] prices) {
if (prices == null || prices.length == 0) {
return 0;
}
int n = prices.length;
// n天最多进行n/2笔交易
// k取交易数最小值即可
k = Math.min(k, n / 2);
// buy[i][j]:持有股票下,到第i天交易j次的最大利润
int[][] buy = new int[n][k + 1];
// sell[i][j]:不持有股票下,到第i天交易j次的最大利润
int[][] sell = new int[n][k + 1];
// 初始化
buy[0][0] = -prices[0];
sell[0][0] = 0;
for (int i = 1; i <= k; i++) {
// /2防止Integer.Min减数时越界
buy[0][i] = sell[0][i] = Integer.MIN_VALUE / 2;
}
// 动态转移
for (int i = 1; i < n; i++) {
// 更新第i天,进行0次交易的最大利润
buy[i][0] = Math.max(buy[i - 1][0], sell[i - 1][0] - prices[i]);
for (int j = 1; j <= k; j++) {
buy[i][j] = Math.max(buy[i - 1][j], sell[i - 1][j] - prices[i]);
// buy[i - 1][j - 1]
sell[i][j] = Math.max(sell[i - 1][j], buy[i - 1][j - 1] + prices[i]);
}
}
// 最大利润一定是sell中的最大值
return Arrays.stream(sell[n - 1]).max().getAsInt();
}
public static void main(String[] args) {
Solution solution = new Solution();
int k = 2;
int[] nums = {3, 2, 6, 5, 0, 3};
System.out.println(solution.maxProfit(k, nums));
}
}
LC309_最佳买卖股票时机含冷冻期
public class Solution {
/**
* 最佳买卖股票时机含冷冻期
* 输入: prices = [1,2,3,0,2]
* 输出: 3
* 解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
*/
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0) {
return 0;
}
int n = prices.length;
// dp[i][j]:第i天最大利润
// 当来到新的一天,会有以下3种状态,所以是二维数组
// j=0说明第i天持有股票
// j=1说明第i天不持有股票,处于冷冻期
// j=2说明第i天不持有股票,不处于冷冻期
int[][] dp = new int[n][3];
// 初始化
// 第0天持有股票:属于买股票
dp[0][0] = -prices[0];
// 第0天不持有股票,处于冷冻期,不存在,假设为0
dp[0][1] = 0;
// 第0天不持有股票,不处于冷冻期,不存在,假设为0
dp[0][2] = 0;
for (int i = 1; i < n; i++) {
// 第i天持有股票:前一天不处于冷冻期+今天买股票;前一天持有股票不操作
dp[i][0] = Math.max(dp[i - 1][2] - prices[i], dp[i - 1][0]);
// 第i天不持有股票,处于冷冻期:只能是前一天持有股票,今天卖出获得收益
dp[i][1] = dp[i - 1][0] + prices[i];
// 第i天不持有股票,不处于冷冻期:今天没有任何操作,取前一天不持有股票两种状态最大值
dp[i][2] = Math.max(dp[i - 1][1], dp[i - 1][2]);
}
return Math.max(dp[n - 1][1], dp[n - 1][2]);
}
public static void main(String[] args) {
Solution solution = new Solution();
int[] nums = {2, 1};
System.out.println(solution.maxProfit(nums));
}
}
最长递增子序列
LC300_最长递增子序列
public class Solution {
/**
* 最长递增子序列
* 输入:nums = [10,9,2,5,3,7,101,18]
* 输出:4
* 解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
*/
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int[] dp = new int[nums.length];
Arrays.fill(dp, 1);
int maxLen = 1;
for (int i = 0; i < nums.length; i++) {
// 从[j,i]计算到i时的最长递增子序列长度
for (int j = 0; j <= i; j++) {
if (nums[j] < nums[i]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
maxLen = Math.max(maxLen, dp[i]);
}
}
}
return maxLen;
}
public static void main(String[] args) {
Solution solution = new Solution();
int[] nums = {10, 9, 2, 5, 3, 7, 101, 18};
System.out.println(solution.lengthOfLIS(nums));
}
}
零钱兑换
LC322_零钱兑换
public class Solution {
/**
* 零钱兑换
* 输入:coins = [1, 2, 5], amount = 11
* 输出:3
* 解释:11 = 5 + 5 + 1
*/
public int coinChange(int[] coins, int amount) {
if (coins == null || coins.length == 0) {
return -1;
}
// dp[i]=选出总额为i的最少硬币数
int[] dp = new int[amount + 1];
// 遍历[1,amount],因为金额从1到amount
for (int i = 1; i <= amount; i++) {
// 预设失败值
dp[i] = amount + 1;
for (int coin : coins) {
if (coin <= i) {
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}
}
return dp[amount] == amount + 1 ? -1 : dp[amount];
}
public static void main(String[] args) {
Solution solution = new Solution();
int[] coins = {1, 2, 5};
int target = 11;
System.out.println(solution.coinChange(coins, target));
}
}
LC72_编辑距离
public class Solution {
/**
* 编辑距离
* 输入:word1 = "horse", word2 = "ros"
* 输出:3
* 解释:
* horse -> rorse (将 'h' 替换为 'r')
* rorse -> rose (删除 'r')
* rose -> ros (删除 'e')
*/
public int minDistance(String word1, String word2) {
int m = word1.length();
int n = word2.length();
// dp[i][j] = word1到i位置转换成word2到j位置的最少步数
int[][] dp = new int[m + 1][n + 1];
// 第一列:word2=null,word1每个位置需要删除的步数=最少步数
for (int i = 1; i <= m; i++) {
dp[i][0] = i;
}
// 第一行:word1=null,word2每个位置需要增加的步数=最少步数
for (int i = 1; i <= n; i++) {
dp[0][i] = i;
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
// 该位置元素相同,最少步数由上一次dp决定
if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1];
} else {
// “dp[i-1][j-1]表示替换操作,dp[i-1][j] 表示删除操作,dp[i][j-1] 表示插入操作。”的补充理解:
// word1="horse",word2="ros",且 dp[5][3] 为例:
// (1) dp[i-1][j-1]=dp[4][2]=替换,即先将 word1 的前 4 个字符 hors 转换为 word2 的前 2 个字符 ro
// (2) dp[i][j-1]=dp[5][2]=插入,即先将 word1 的前 5 个字符 horse 转换为 word2 的前 2 个字符 ro
// (3) dp[i-1][j]=dp[4][3]=删除,即先将 word1 的前 4 个字符 hors 转换为 word2 的前 3 个字符 ros
// 有三种操作状态,取三者最小+1
dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
}
}
}
return dp[m][n];
}
public static void main(String[] args) {
Solution solution = new Solution();
String w1 = "horse";
String w2 = "ros";
System.out.println(solution.minDistance(w1, w2));
}
}
实现一个简单并查集
public class QuickUnionFind {
private int[] roots;
public QuickUnionFind(int n) {
roots = new int[n];
for (int i = 0; i < n; i++) {
roots[i] = i;
}
}
private int findRoot(int i) {
// 找出最大的老大
int root = i;
while (root != roots[root]) {
root = roots[root];
}
// 路径压缩,i经过的所有结点都指向老大
while (i != roots[i]) {
int temp = roots[i];
roots[i] = root;
i = temp;
}
// 返回老大
return root;
}
private boolean connected(int p, int q) {
return findRoot(p) == findRoot(q);
}
private void union(int p, int q) {
int root1 = findRoot(p);
int root2 = findRoot(q);
roots[root2] = root1;
}
}
岛屿数量
LC200_岛屿数量
public class Solution {
/**
* 岛屿数量
* 输入:grid = [
* ["1","1","0","0","0"],
* ["1","1","0","0","0"],
* ["0","0","1","0","0"],
* ["0","0","0","1","1"]
* ]
* 输出:3
*/
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}
int count = 0;
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[0].length; j++) {
// 遇到1,就将四周的岛屿染色为0;计数+1
if (grid[i][j] == '1') {
dfs(grid, i, j);
count++;
}
}
}
return count;
}
private void dfs(char[][] grid, int i, int j) {
if (i < 0 || i > grid.length - 1 || j < 0 || j > grid[0].length - 1 || grid[i][j] != '1') {
return;
}
// 染色,成0
grid[i][j] = '0';
dfs(grid, i + 1, j);
dfs(grid, i - 1, j);
dfs(grid, i, j + 1);
dfs(grid, i, j - 1);
}
public static void main(String[] args) {
Solution solution = new Solution();
char[][] grid = {{'1', '1', '0', '0', '0'}, {'1', '1', '0', '0', '0'}, {'0', '0', '1', '0', '0'}, {'0', '0', '0', '1', '1'},};
System.out.println(solution.numIslands(grid));
}
}
public class Solution1 {
/**
* 岛屿数量
* 输入:grid = [
* ["1","1","0","0","0"],
* ["1","1","0","0","0"],
* ["0","0","1","0","0"],
* ["0","0","0","1","1"]
* ]
* 输出:3
*/
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}
int m = grid.length;
int n = grid[0].length;
UnionFind uf = new UnionFind(grid);
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
// 遇见'1'=岛屿,就放入并查集中
if (grid[i][j] == '1') {
if (i - 1 >= 0 && grid[i - 1][j] == '1') {
uf.union(i * n + j, (i - 1) * n + j);
}
if (i + 1 < m && grid[i + 1][j] == '1') {
uf.union(i * n + j, (i + 1) * n + j);
}
if (j - 1 >= 0 && grid[i][j - 1] == '1') {
uf.union(i * n + j, i * n + j - 1);
}
if (j + 1 < n && grid[i][j + 1] == '1') {
uf.union(i * n + j, i * n + j + 1);
}
}
}
}
return uf.getCount();
}
/**
* 定义并查集数据结构
*/
class UnionFind {
int count;
int[] parent;
public UnionFind(char[][] grid) {
count = 0;
int m = grid.length;
int n = grid[0].length;
// 铺平成一维
parent = new int[m * n];
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (grid[i][j] == '1') {
// 有几行,一维数组下标=行数*n+j
parent[i * n + j] = i * n + j;
// 初始化时,一个'1'表示一个单独岛屿,有几个'1'就有多少个岛屿
++count;
}
}
}
}
public int find(int i) {
int root = i;
while (root != parent[root]) {
root = parent[root];
}
// 路径压缩,i经过的所有结点都指向老大
while (i != parent[i]) {
int temp = parent[i];
parent[i] = root;
i = temp;
}
// 返回老大
return root;
}
public void union(int x, int y) {
int root1 = find(x);
int root2 = find(y);
if (root1 != root2) {
parent[root2] = root1;
// 不相等,连通数-1
count--;
}
}
public int getCount() {
return count;
}
}
public static void main(String[] args) {
Solution1 solution = new Solution1();
char[][] grid = {{'1', '1', '0', '0', '0'}, {'1', '1', '0', '0', '0'}, {'0', '0', '1', '0', '0'}, {'0', '0', '0', '1', '1'},};
System.out.println(solution.numIslands(grid));
}
}
LC547_省份数量
public class Solution {
/**
* 省份数量
* 输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
* 输出:2
*/
public int findCircleNum(int[][] isConnected) {
// 初始化并查集
int[] roots = new int[isConnected.length];
for (int i = 0; i < roots.length; i++) {
roots[i] = i;
}
// isConnected是N*N的,有几行就有几个城市
// 初始化省份数量
int count = isConnected.length;
for (int i = 0; i < isConnected.length; i++) {
for (int j = i + 1; j < isConnected.length; j++) {
// 省份间相互连通,连通的省份数量减1
if (isConnected[i][j] == 1 && union(roots, i, j)) {
count--;
}
}
}
return count;
}
private int find(int[] roots, int i) {
if (roots[i] != i) {
roots[i] = find(roots, roots[i]);
}
return roots[i];
}
private boolean union(int[] roots, int i, int j) {
int root1 = find(roots, i);
int root2 = find(roots, j);
if (root1 != root2) {
roots[root2] = root1;
return true;
}
return false;
}
public static void main(String[] args) {
Solution solution = new Solution();
int[][] nums = {
{1, 1, 0},
{1, 1, 0},
{0, 0, 1}
};
System.out.println(solution.findCircleNum(nums));
}
}
LRU
LC146_LRU缓存
public class LRUCache {
private int size;
private int capacity;
private Map<Integer, DLinkedNode> cache;
private DLinkedNode head;
private DLinkedNode tail;
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
this.cache = new HashMap<>(capacity);
// 题目规定key、value>=0,这里可以传-1表示头尾结点
this.head = new DLinkedNode(-1, -1);
this.tail = new DLinkedNode(-1, -1);
this.head.next = tail;
this.tail.pre = head;
}
public int get(int key) {
if (size == 0) {
return -1;
}
DLinkedNode node = cache.get(key);
if (node == null) {
return -1;
}
deleteNode(node);
removeToHead(node);
return node.value;
}
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
if (node != null) {
node.value = value;
deleteNode(node);
removeToHead(node);
return;
}
if (size == capacity) {
// 细节:容量满了,先清理缓存,再删除末尾结点
cache.remove(tail.pre.key);
deleteNode(tail.pre);
size--;
}
node = new DLinkedNode(key, value);
cache.put(key, node);
// 更新缓存
removeToHead(node);
size++;
}
private void deleteNode(DLinkedNode node) {
node.pre.next = node.next;
node.next.pre = node.pre;
}
private void removeToHead(DLinkedNode node) {
node.next = head.next;
head.next.pre = node;
head.next = node;
node.pre = head;
}
/**
* 定义双向链表结点数据结构
*/
private class DLinkedNode {
int key;
int value;
DLinkedNode pre;
DLinkedNode next;
public DLinkedNode(int key, int value) {
this.key = key;
this.value = value;
}
public DLinkedNode() {
}
}
}
LC460_LFU缓存
public class LFUCache {
private int size;
private int capacity;
private Map<Integer, Node> cache;
/**
* 频次对应的双向链表
*/
private Map<Integer, DoubleLinkedList> freqMap;
/**
* 当前最小值
*/
private int min;
public LFUCache(int capacity) {
this.cache = new HashMap<>(capacity);
this.freqMap = new HashMap<>();
this.capacity = capacity;
}
public int get(int key) {
Node node = cache.get(key);
if (node == null) {
return -1;
}
// node增加频次
addFreq(node);
return node.value;
}
public void put(int key, int value) {
if (capacity == 0) {
return;
}
Node node = cache.get(key);
// node存在就更新频次
if (node != null) {
node.value = value;
addFreq(node);
} else {
// node不存在
// 链表满啦,移除最不经常使用的=移除min对应的链表
if (size == capacity) {
DoubleLinkedList minList = freqMap.get(min);
cache.remove(minList.tail.pre.key);
minList.removeNode(minList.tail.pre);
size--;
}
node = new Node(key, value);
cache.put(key, node);
// 获取频次为1的链表
DoubleLinkedList linkedList = freqMap.get(1);
if (linkedList == null) {
linkedList = new DoubleLinkedList();
freqMap.put(1, linkedList);
}
linkedList.addNode(node);
size++;
// node不存在,更新最不长使用频次=1
min = 1;
}
}
private void addFreq(Node node) {
// 从原freq对应的链表里移除, 并更新min
int freq = node.freq;
DoubleLinkedList linkedList = freqMap.get(freq);
linkedList.removeNode(node);
// freq = min 且 原freq对应的链表为空
if (freq == min && linkedList.head.next == linkedList.tail) {
min = freq + 1;
}
// 更新freq
node.freq++;
linkedList = freqMap.get(freq + 1);
if (linkedList == null) {
linkedList = new DoubleLinkedList();
freqMap.put(freq + 1, linkedList);
}
linkedList.addNode(node);
}
class DoubleLinkedList {
Node head;
Node tail;
public DoubleLinkedList() {
head = new Node();
tail = new Node();
head.next = tail;
tail.pre = head;
}
void removeNode(Node node) {
node.pre.next = node.next;
node.next.pre = node.pre;
}
void addNode(Node node) {
node.next = head.next;
head.next.pre = node;
head.next = node;
node.pre = head;
}
}
/**
* 结点
*/
class Node {
int key;
int value;
int freq = 1;
Node pre;
Node next;
public Node() {
}
public Node(int key, int value) {
this.key = key;
this.value = value;
}
}
}