更多答案也可以关注我的GitHub算法仓库——Algorithm
基础数据结构:
class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
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;
}
}
155.最小栈
使用 辅助栈,与元素栈同步插入与删除,用于存储与每个元素对应的最小值。
- 当一个元素要入栈时,我们取当前辅助栈的栈顶存储的最小值,与当前元素比较得出最小值,将这个最小值插入辅助栈中
- 当一个元素要出栈时,我们把辅助栈的栈顶元素也一并弹出
- 在任意一个时刻,栈内元素的最小值就存储在辅助栈的栈顶元素中
class MinStack {
ArrayDeque<Integer> stack;
ArrayDeque<Integer> minStack;
public MinStack() {
stack = new ArrayDeque<>();
minStack = new ArrayDeque<>();
minStack.push(Integer.MAX_VALUE);
}
public void push(int val) {
stack.push(val);
minStack.push(Math.min(val, minStack.peekFirst()));
}
public void pop() {
stack.pop();
minStack.pop();
}
public int top() {
return stack.peekFirst();
}
public int getMin() {
return minStack.peekFirst();
}
}
41.缺失的第一个正数
缺失的正数一定在[1, nums.length+1]中,由于题目限制空间复杂度为O(1),我们需要修改原数组,结合数组下标的特性来替代哈希表。
public int firstMissingPositive(int[] nums) {
int l = nums.length;
//将所有小于等于0的数置为l+1
for (int i = 0; i < l; i++) {
if (nums[i] <= 0) {
nums[i] = l+1;
}
}
for (int i = 0; i < l; i++) {
int temp = Math.abs(nums[i]);
if (temp <= l) {
nums[temp-1] = -Math.abs(nums[temp-1]);
}
}
//寻找下标对应的第一个正数,返回该下标
for (int i = 0; i < l; i++) {
if (nums[i] > 0) {
return i+1;
}
}
return l+1;
}
718.最长重复子数组
这里要注意是连续子数组,不同于字符串的子序列,所以状态转移方程中不相等的情况下:dp[i][j] = 0;
- 动规:时间O(MN),空间O(MN)
- 滑动窗口: O((N+M)×min(N,M)) ,空间 O(1)
public int findLength(int[] nums1, int[] nums2) {
int[][] dp = new int[nums1.length+1][nums2.length+1];
int res = 0;
for (int i = 1; i <= nums1.length ; i++) {
for (int j = 1; j <= nums2.length; j++) {
if (nums1[i-1] == nums2[j-1]) {
dp[i][j] = 1 + dp[i-1][j-1];
} else {
dp[i][j] = 0;
}
res = Math.max(res, dp[i][j]);
}
}
return res;
}
234.回文链表
时间O(n),空间O(1),思路还是比较好想的
- 快慢指针找中点
- 翻转一半
- 再遍历比较
class Solution {
public boolean isPalindrome(ListNode head) {
if (head.next == null) {
return true;
}
ListNode slow = head, fast = head, slowPre = head;
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slowPre = slow;
slow = slow.next;
}
ListNode l2 = slow.next, l1;
if (fast.next != null) {
slowPre = slow;
}
slowPre.next = null;
l1 = reverseList(head);
while (l1 != null && l2 != null) {
if (l1.val != l2.val) {
return false;
}
l1 = l1.next;
l2 = l2.next;
}
return true;
}
public ListNode reverseList(ListNode head) {
ListNode prev = null, next = null;
while (head != null) {
next = head.next;
head.next = prev;
prev = head;
head = next;
}
return prev;
}
}
78.子集
记原序列中元素的总数为 n。原序列中的每个数字 ai 的状态可能有两种,即「在子集中」和「不在子集中」。
我们用 1 表示「在子集中」,0 表示不在子集中,那么每一个子集可以对应一个长度为 n 的 0/1 序列,第 i 位表示 ai是否在子集中。
(这里会用到位运算)
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> t = new ArrayList<>();
int n = nums.length;
for (int i = 0; i < (1 << n); i++) {
t.clear();
for (int j = 0; j < n; j++) {
if ((i & (1 << j)) != 0) {
t.add(nums[j]);
}
}
res.add(new ArrayList<>(t));
}
return res;
}
112.路径总和
递归
class Solution {
public boolean hasPathSum(TreeNode root, int sum) {
if (root == null) {
return false;
}
if (root.left == null && root.right == null) {
return sum == root.val;
}
return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
}
}
98.验证二叉搜索树
两个递归思路:
- 递归,但要判断节点的上下界
- 递归,中序遍历需递增
class Solution {
Long last = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if (root == null) {
return true;
}
if (isValidBST(root.left)) {
if (last < root.val) {
last = root.val + 0l;
return isValidBST(root.right);
}
}
return false;
}
}
32.最长有效括号
也是一道动态规划,但是动态规划的转移方程比较难想,dp[i]代表以s.charAt(i)结尾时的最长有效括号,由于题目要求为最长且连续,那么:
- 如果
dp[i] == '('
,那么在当前位置一定是0 - 如果
dp[i] == ')'
,那么需要根据dp[i-1]来找到前一个为被匹配的 '(' - 如果存在,则
dp[i] = 2 + dp[i-1] + dp[i - dp[i-1] - 2]
这里的dp[i - dp[i-1] - 2]
为上一个未被匹配的'(',也就是上一个断连之前的最长连续有效括号值 - 其中还需要判断数组下标-1的情况
public int longestValidParentheses(String s) {
int len = s.length();
if (len <= 1) {
return 0;
}
int res = 0;
int[] dp = new int[len];
for (int i = 1; i < len; i++) {
if (s.charAt(i) == ')' && i - dp[i-1] > 0 && s.charAt(i - dp[i-1] -1) == '(') {
dp[i] += (2 + dp[i-1]);
if (i - dp[i-1] > 1) {
dp[i] += dp[i - dp[i-1] - 2];
}
res = Math.max(res, dp[i]);
}
}
return res;
}
101.对称二叉树
两个指针,同步对称移动
class Solution {
public boolean isSymmetric(TreeNode root) {
return dfs(root, root);
}
public boolean dfs(TreeNode q, TreeNode p) {
if (q == null && p == null) {
return true;
}
if (q == null || p == null) {
return false;
}
return q.val == p.val && dfs(q.left, p.right) && dfs(q.right, p.left);
}
}
322.零钱兑换
区间dp,dp数组定义不是根据硬币,而是根据金额:dp[i] 代表总金额为 i 时所需的最小硬币个数
状态转移方程也是有些难度的,将数组初始化为无穷大,代表此金额无法使用所给金币凑出,dp[0] = 0
- 如果当前金额可以被某一种硬币凑出,则:
dp[i] = Math.min(dp[i - coin] + 1, dp[i]);
public int coinChange(int[] coins, int amount) {
if (amount == 0) {
return 0;
}
int[] dp = new int[amount+1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
for (int coin : coins) {
for (int i = coin; i <= amount ; i++) {
if (dp[i - coin] != Integer.MAX_VALUE) {
dp[i] = Math.min(dp[i - coin] + 1, dp[i]);
}
}
}
return dp[amount] < Integer.MAX_VALUE ? dp[amount] : -1;
}
22.括号生成
题目要求给出所有可能,暴力dfs即可
class Solution {
List<String> res = new ArrayList<>();
public List<String> generateParenthesis(int n) {
dfs(n, n, "");
return res;
}
public void dfs(int left, int right, String s) {
for (int i = 0; i < 2; i++) {
if (left == 0 && right == 0) {
res.add(s);
break;
}
if (i == 0 && left > 0) {
dfs(left - 1, right, s + "(");
}else if (i == 1 && right > left) {
dfs(left, right - 1, s + ")");
}
}
}
}
169.多数元素
Boyer-Moore 投票算法,时间O(n),空间O(1)
算法过程:
我们维护一个候选众数 candidate 和它出现的次数 count。初始时 candidate 可以为任意值,count 为 0;
我们遍历数组 nums 中的所有元素,对于每个元素 x,在判断 x 之前,如果 count 的值为 0,我们先将 x 的值赋予candidate,随后我们判断 x:
- 如果 x 与 candidate 相等,那么计数器 count 的值增加 1;
- 如果 x 与 candidate 不等,那么计数器 count 的值减少 1。
在遍历完成后,candidate 即为整个数组的众数。
投票算法证明:
- 如果候选人不是maj 则 maj,会和其他非候选人一起反对 会反对候选人,所以候选人一定会下台(maj==0时发生换届选举)
- 如果候选人是maj , 则maj 会支持自己,其他候选人会反对,同样因为maj 票数超过一半,所以maj 一定会成功当选
public int majorityElement(int[] nums) {
int cand = 0, count = 0;
for (int i = 0; i < nums.length; i++) {
if (count == 0) {
cand = nums[i];
}
count += (cand == nums[i] ? 1 : -1);
}
return cand;
}
48.旋转图像
原地旋转:将矩形分为四块,两个for循环一块的量,但是每次swap四个块的坐标
就是四个对应坐标的转换方程有些难推
public void rotate(int[][] matrix) {
int n = matrix.length;
for (int i = 0; i < n/2; i++) {
for (int j = 0; j < (n+1)/2; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[n - j - 1][i];
matrix[n - j - 1][i] = matrix[n - i - 1][n - j - 1];
matrix[n - i - 1][n - j - 1] = matrix[j][n - i - 1];
matrix[j][n - i - 1] = temp;
}
}
}
43.字符串相乘
很恶心的题,费劲又麻烦,没啥特别简洁的套路
public String multiply(String num1, String num2) {
if (num1.equals("0") || num2.equals("0")) {
return "0";
}
int[] res = new int[num1.length() + num2.length()];
for (int i = num1.length() - 1; i >= 0; i--) {
int n1 = num1.charAt(i) - '0';
for (int j = num2.length() - 1; j >= 0; j--) {
int n2 = num2.charAt(j) - '0';
int sum = (res[i + j + 1] + n1 * n2);
res[i + j + 1] = sum % 10;
res[i + j] += sum / 10;
}
}
StringBuilder result = new StringBuilder();
for (int i = 0; i < res.length; i++) {
if (i == 0 && res[i] == 0) continue;
result.append(res[i]);
}
return result.toString();
}
64.最小路径和
很简单常规的二维dp,转移方程 dp[i][j] = grid[i][j] + Math.min(dp[i][j-1], dp[i-1][j])
public int minPathSum(int[][] grid) {
int row = grid.length;
int col = grid[0].length;
int[][] dp = new int[row][col];
dp[0][0] = grid[0][0];
for (int i = 1; i < row; i++) {
dp[i][0] = dp[i-1][0] + grid[i][0];
}
for (int i = 1; i < col; i++) {
dp[0][i] = dp[0][i-1] + grid[0][i];
}
for (int i = 1; i < row; i++) {
for (int j = 1; j < col; j++) {
dp[i][j] = grid[i][j] + Math.min(dp[i][j-1], dp[i-1][j]);
}
}
return dp[row-1][col-1];
}
226. 翻转二叉树
典型递归
public TreeNode invertTree(TreeNode root) {
if (root != null) {
invertTree(root.left);
invertTree(root.right);
TreeNode tree = root.left;
root.left = root.right;
root.right = tree;
}
return root;
}
165.比较版本号
String.split分割,但是注意特殊字符前加\\
class Solution {
public int compareVersion(String version1, String version2) {
String[] s1 = version1.split("\\.");
String[] s2 = version2.split("\\.");
int len1 = s1.length;
int len2 = s2.length;
for (int i = 0; i < len1 && i < len2; i++) {
if (Integer.valueOf(s1[i]) > Integer.valueOf(s2[i])) {
return 1;
}
if (Integer.valueOf(s1[i]) < Integer.valueOf(s2[i])) {
return -1;
}
}
if (len1 > len2 && remain(s1, len2) > 0) {
return 1;
}else if (len1 < len2 && remain(s2, len1) > 0) {
return -1;
}
return 0;
}
public int remain(String[] arr, int start) {
StringBuilder sb = new StringBuilder("");
for (int i = start; i < arr.length; i++) {
sb.append(arr[i]);
}
return Integer.valueOf(sb.toString());
}
}
34.在排序数组中查找元素的第一个和最后一个位置
在使用二分查找第一次查找到目标值之后继续二分查找
那么第一次出现的即为一直二分后最左的位置,最后一次出现即为第一次比target大的数的下标-1
class Solution {
public int[] searchRange(int[] nums, int target) {
int leftIdx = binarySearch(nums, target, true);
int rightIdx = binarySearch(nums, target, false) - 1;
if (leftIdx <= rightIdx && rightIdx < nums.length && nums[leftIdx] == target && nums[rightIdx] == target) {
return new int[]{leftIdx, rightIdx};
}
return new int[]{-1, -1};
}
public int binarySearch(int[] nums, int target, boolean lower) {
int left = 0, right = nums.length - 1, ans = nums.length;
while (left <= right) {
int mid = (left + right)/2;
if (nums[mid] > target || (lower && nums[mid] >= target)) {
right = mid -1;
ans = mid;
} else {
left = mid + 1;
}
}
return ans;
}
}
83.删除排序链表中的重复元素
链表基操
public ListNode deleteDuplicates(ListNode head) {
ListNode node = head;
while (node != null) {
ListNode cur = node.next;
while (cur != null && node.val == cur.val) {
cur = cur.next;
}
node.next = cur;
node = node.next;
}
return head;
}
39.组合总和
由于题目要求所有情况,暴力dfs + 剪枝 + 回溯 即可
class Solution {
List<List<Integer>> res = new ArrayList<>();
int[] arr;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
arr = candidates;
dfs(new ArrayList<>(), target, 0);
return res;
}
public void dfs(List<Integer> list, int val, int index) {
if (val < 0) {
return;
}
if (val == 0) {
res.add(new ArrayList<>(list));
}
for (int i = index; i < arr.length; i++) {
list.add(arr[i]);
dfs(list, val - arr[i], i);
list.remove(list.size() - 1);
}
}
}
1 条评论
每次看到你的文章,我都觉得时间过得好快。http://www.pig868.com