1. 两数之和

思路一:暴力遍历所有组合

class Solution {
public int[] twoSum(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] == target) {
return new int[] {i, j};
}
}
}
//没找到
return new int[] {-1, -1};
}
}

思路二:利用map,key存储元素凑成target所需的差值,value存储元素下标

class Solution {
public int[] twoSum(int[] nums, int target) {
//value代表下标,key代表target与当前元素只差,即:target - nums[value]
Map<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < nums.length; i++) {
if (map.containsKey(nums[i])) {
return new int[] {map.get(nums[i]), i};
} else {
map.put(target - nums[i], i);
}
}
//没找到
return new int[] {-1, -1};
}
}

2. 两数相加

思路:直接模拟加法

class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode cur1 = l1;
ListNode cur2 = l2;
//哑结点
ListNode dummy = new ListNode(-1);
ListNode cur = dummy; //进位
int carry = 0;
while (cur1 != null || cur2 != null || carry != 0) {
int val1 = cur1 != null ? cur1.val : 0;
int val2 = cur2 != null ? cur2.val : 0;
int sum = val1 + val2 + carry;
int reminder = sum % 10;
carry = sum / 10; //尾插法
ListNode tempNode = new ListNode(reminder);
cur.next = tempNode;
cur = cur.next;; if (cur1 != null) cur1 = cur1.next;
if (cur2 != null) cur2 = cur2.next;
} return dummy.next;
}
}

3. 无重复字符的最长子串

class Solution {
public int lengthOfLongestSubstring(String s) {
//窗口左右边界,左闭右开
int left = 0, right = 0;
//存储字符及其在窗口中的个数
Map<Character, Integer> window = new HashMap<>();
int maxLen = 0; while (right < s.length()) {
//窗口扩大
char rightChar = s.charAt(right);
right++;
window.put(rightChar, window.getOrDefault(rightChar, 0) + 1); //如果新加入窗口的字符个数大于1次,窗口应该收缩
while(window.get(rightChar) > 1) {
char leftChar = s.charAt(left);
left++;
window.put(leftChar, window.get(leftChar) - 1);
} maxLen = Math.max(maxLen, right - left);
} return maxLen;
}
}

4. 寻找两个正序数组的中位数

思路一:直接合并两个有序数组,根据数组长度求中位数。时间复杂为O(m+n),空间复杂度为O(m+n)

class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length;
int n = nums2.length; int[] tempArr = new int[m + n];
int i = 0, j = 0;
int k = 0;
while(i < m && j < n) {
if (nums1[i] < nums2[j]) {
tempArr[k++] = nums1[i++];
} else {
tempArr[k++] = nums2[j++];
}
} //nums1还有剩余
while (i < m) {
tempArr[k++] = nums1[i++];
} //nums2还有剩余
while (j < n) {
tempArr[k++] = nums2[j++];
} int midIndex = (m + n) / 2;
double mid = 0;
//长度为偶数
if ((m + n) % 2 == 0) {
mid = (tempArr[midIndex - 1] + tempArr[midIndex]) / 2.0;
} else {
mid = tempArr[midIndex];
} return mid;
}
}

思路二:无需真正合并数组,先根据两个数组的长度确定中位数是合并后数组中的第几个数,然后根据规则将指向两个数组的下标移动对应次数就好。时间复杂为O(m+n),空间复杂度为O(1)

class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length;
int n = nums2.length; //i, j分别表示nums1和nums2的分割线,i为[0, m],j为[0, n];
//[0, i)表示nums1中已经遍历过的下标,对应j类似
int i = 0, j = 0;
//记录最新遍历的两个数
int newest = 0, secondNew = 0;
//奇数:中位数是第(m+n)/2 + 1个数(下标从1开始)
//偶数:中位数是第(m+n)/2和第(m+n)/2 + 1数的平均数(下标从1开始)
int cnt = (m + n) / 2 + 1;
for (int k = 0; k < cnt; k++) {
secondNew = newest;
// j >= n 表示指向nums2已经没有元素
if (j >= n || (i < m && nums1[i] < nums2[j])) {
newest = nums1[i];
i++;
} else {
newest = nums2[j];
j++;
}
}
//偶数
if ((m + n) % 2 == 0) {
return (newest + secondNew) / 2.0;
} else {
return newest;
}
}
}

终于说服自己

思路三:使用二分法直接在两个数组中找中位数分割线,使得nums1nums2中分割线满足以下性质即可根据分割线左右的数来确定中位数:

前置:m = nums1.lengthn = nums2.length。设inums1中分割线,则取值为[0, m],表示分割线左侧元素下标为[0, i-1],分割线右侧元素下标为[i, m-1];设jnums2中分割线,....。

  • m+n为偶数: i + j = (m + n )/2 ,为奇数:i + j = (m + n)/2 + 1

  • 分割线左侧元素小于等于分割线右侧元素。由于两个数组均为正序数组,则只需要要求:nums1[i-1] <= nums2[j] && nums2[j-1] <= nums1[i];由于该条件等价于在[0, m]中找到最大的i使得nums1[i-1] <= nums2[j],因此可以使用二分查找。(证明:假设我们已经找到了满足条件的最大i,使得nums1[i-1] <= nums2[j],那么此时必有nums[i] > nums2[j],进而有nums[i] > nums2[j-1]

分割线找到后,若m+n为奇数,分割线左侧的最大值即为中位数;若为偶数,分割线左侧的最大值与分割线右侧的最小值的平均数即为中位数。时间复杂度:O(log(min(m, n))),空间复杂度:O(1)

class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
// 始终保证nums1为较短的数组,nums1过长可能导致j为负数而越界
if (nums1.length > nums2.length) {
int[] temp = nums1;
nums1 = nums2;
nums2 = temp;
} int m = nums1.length;
int n = nums2.length; // m+n 为奇数,分割线要求左侧有 (m+n)/2 + 1 个元素
// m+n 为偶数,分割线要求左侧有 (m+n)/2 个元素
// 两种情况其实可以统一写作 (m+n+1)/2,表示对(m+n)/2向上取整
// 对整数来说,向上取整等于:(被除数 + (除数 - 1)) / 除数
// 也可以使用Math类中提供的库函数
int leftTotal = (m + n + 1) / 2;
int left = 0, right = m;
while (left < right) {
// +1 向上取整避免 left + 1 = right 时可能无法继续缩小区间而陷入死循环
int i = left + (right - left + 1) / 2;
int j = leftTotal - i; //要找最大i,使得nums1[i-1] <= nums2[j]
//使用对立面缩小区间
if (nums1[i - 1] > nums2[j]) {
// [i+1, m]均不满足
right = i - 1;
} else {
// i满足说明[0, i-1]均不为满足条件的最大i,舍去以缩小区间
left = i;
}
} //退出循环时left=right,表示最终nums1中分割线的位置
int i = left;
//nums2中分割线的位置
int j = leftTotal - left;
System.out.println(i); //判断极端情况
int nums1LeftMax = (i == 0) ? Integer.MIN_VALUE : nums1[i - 1]; //nums1分割线左边没有元素
int nums2LeftMax = (j == 0) ? Integer.MIN_VALUE : nums2[j - 1]; //nums2分割线左边没有元素
int nums1RightMin = (i == m) ? Integer.MAX_VALUE : nums1[i]; //nums1分割线右边没有元素
int nums2RightMin = (j == n) ? Integer.MAX_VALUE : nums2[j]; //nums2分割线右边没有元素 if ((m + n) % 2 == 0) {
return (Math.max(nums1LeftMax, nums2LeftMax) + Math.min(nums1RightMin, nums2RightMin)) / 2.0;
} else {
return Math.max(nums1LeftMax, nums2LeftMax);
}
}
}

备注:这里使用的二分法和二分法查找x的平方根使用的方法很像,都是要查找满足条件的最大值。

参考:官方题解:方法二wei哥:二分查找定位短数组的「分割线」(Java )

5. 最长回文子串

思路:动态规划获取任意两个区间的子串是否为回文子串,如果是则记录开始下标和长度

class Solution {
public String longestPalindrome(String s) {
int len = s.length();
int maxLen = 0;
int start = 0; //dp[i][j]: 字符串s[i, j]是否为回文字符串
boolean[][] dp = new boolean[len][len]; for (int j = 0; j < len; j++) {
for (int i = 0; i <= j; i++) {
if (s.charAt(i) == s.charAt(j) && (j - i <= 2 || dp[i + 1][j - 1])) {
dp[i][j] = true;
if (j - i + 1 > maxLen) {
maxLen = j - i + 1;
start = i;
}
}
}
} return s.substring(start, start + maxLen);
}
}

10. 正则表达式匹配

class Solution {
public boolean isMatch(String s, String p) {
int sLen = s.length();
int pLen = p.length(); //状态:dp[i][j] 表示s的前i个字符和p的前j个字符能否匹配,即 s[0, i-1] 和 p[0, j-1] 能否匹配
boolean[][] dp = new boolean[sLen + 1][pLen + 1]; //前置:i, j分别代表dp的横、纵下标,对应的s、p的下标都应减去1
//初始值:dp 默认都为flase
//dp[0][0] = true, 即s和p都为空
//dp[i][0] = false, 其中i >= 1, 即s不为空p为空
//dp[0][1] = false, 由于p[0]不能为*,s为空,p只有一个字符且不为'*'的情况下必然不能匹配成功
//s为空,p不为空且p[0, j-1]以'*'结尾时,还不能直接断定dp的值。
//因为'*'可以选择将它前面的字符匹配零次以消除'*'前面的字符。
//因此 dp[0][j] = dp[0][j - 2], j >= 2。若s为空,p不为空且 p[0, j-1] 不以'*'结尾,那么有:
//dp[0][j] = false, j >= 1。
dp[0][0] = true;
for (int j = 2; j < pLen + 1; j++) {
if (p.charAt(j - 1) == '*') {
dp[0][j] = dp[0][j - 2];
}
} //状态转移:
//当 s[0, i-1] 和 p[0, j-1] 的末尾字符相等或p的末尾字符为'.',则有:dp[i][j] = dp[i - 1][j - 1];
//当 p[0, j-1] 的末尾字符即 p[j - 1] 为'*'需要讨论:
// 若 s[i - 1] 与 p[j - 2] 相等,如:s(ab), p(cab*),那么*可以选择将b 重复一次 则s末尾的b和p末尾的b*匹配抵消,
// 则有:dp[i][j] = dp[i - 1][j - 2];
// 同时*也可以选择将b 重复多次 以匹配抵消s中最后一个b,此时p虽然也损失一个b但任然还剩多个b,可以将其看成b*
// 故有:dp[i][j] = dp[i - 1][j];
// 此外*也可以选择将b 去掉,故:dp[i][j] = dp[i][j - 2]
// 综上:dp[i - j] = dp[i - 1][j - 2] || dp[i - 1]dp[j] || dp[i][j - 2]
// 若 p[j - 2] 为 '.',同上
// 若 s[i - 1] 与 p[j - 2] 不等且后者不为'.',如:s(ab), p(eabd*),那么*可以选择将d 去掉 ,
// 则有:dp[i][j] = dp[i][j - 2]
for (int j = 1; j < pLen + 1; j++) {
for (int i = 1; i < sLen + 1; i++) {
if (s.charAt(i - 1) == p.charAt(j - 1) || p.charAt(j - 1) == '.') {
dp[i][j] = dp[i - 1][j - 1];
} else if (p.charAt(j - 1) == '*') {
if (s.charAt(i - 1) == p.charAt(j - 2) || p.charAt(j - 2) == '.') {
dp[i][j] = dp[i - 1][j -2] || dp[i - 1][j] || dp[i][j - 2];
} else {
dp[i][j] = dp[i][j - 2];
}
}
}
} return dp[sLen][pLen];
}
}

推荐题解:「手画图解」动态规划,需要仔细的分情况讨论

11. 盛最多水的容器

class Solution {
public int maxArea(int[] height) {
int left = 0, right = height.length - 1; int maxArea = 0;
while (left < right) {
int width = right - left;
int high = Math.min(height[left], height[right]);
int area = width * high;
maxArea = Math.max(area, maxArea); //左边小则直接去掉,因为它和右边剩余的任意一个元素组成的面积都不会比当前更大
if (height[left] < height[right]) {
left++;
//去掉右边
} else {
right--;
}
} return maxArea;
}
}

推荐题解:O(n) 双指针解法:理解正确性、图解原理(C++/Java)

15. 三数之和

class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new LinkedList<>();
if (nums == null || nums.length < 3) {
return res;
} //排序很重要
Arrays.sort(nums); int len = nums.length;
for (int i = 0; i < len - 2; i++) { //最后两个数不用判断
//三数之和一定大于0,后序必然不存在为0的组合
if (nums[i] > 0) break;
//去重
if (i > 0 && nums[i] == nums[i - 1]) continue; // left right 表示了 i 的右侧闭合区间
int left = i + 1;
int right = len - 1; while (left < right) {
int target = nums[i] + nums[left] + nums[right]; if (target == 0) {
res.add(Arrays.asList(new Integer[] {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--;
} else if (target < 0) {
left++;
} else if (target > 0) {
right--;
}
}
} return res;
}
}

推荐题解:画解算法:15. 三数之和

17. 电话号码的字母组合

回溯法即可

class Solution {
public List<String> letterCombinations(String digits) {
if (digits == null || digits.length() < 1) {
return res;
} Map<Character, Character[]> map = new HashMap<>() {
{
put('2', new Character[] {'a', 'b', 'c'});
put('3', new Character[] {'d', 'e', 'f'});
put('4', new Character[] {'g', 'h', 'i'});
put('5', new Character[] {'j', 'k', 'l'});
put('6', new Character[] {'m', 'n', 'o'});
put('7', new Character[] {'p', 'q', 'r', 's'});
put('8', new Character[] {'t', 'u', 'v'});
put('9', new Character[] {'w', 'x', 'y', 'z'});
}
};
StringBuilder track = new StringBuilder(); dfs(map, digits, 0, track);
return res;
} //存放最终的结果
private List<String> res = new LinkedList<>();
//回溯法获取所有结果
private void dfs(Map<Character, Character[]> map, String digits, int index, StringBuilder track) {
if (digits.length() == index) {
res.add(track.toString());
return;
} Character[] charArr = map.get(digits.charAt(index));
for (int i = 0; i < charArr.length; i++) {
//选择
track.append(charArr[i]);
dfs(map, digits, index+1, track);
//撤销选择
track.deleteCharAt(index);
}
}
}

19. 删除链表的倒数第 N 个结点

思路:快慢指针都指向头,快指针先移动n,接着快慢指针一起向后移动,直至快指针到达末尾,此时根据满指针即可删除倒数第n个结点。

/**
* Definition for singly-linked list.
* public 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 Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
//删除的可能是头结点,所以需要哑结点
ListNode dummyNode = new ListNode(-1);
dummyNode.next = head; ListNode fast =dummyNode, slow = dummyNode;
for (int i = 0; i < n; i++) {
fast = fast.next;
} while (fast.next != null) {
slow = slow.next;
fast = fast.next;
} //删除结点
slow.next = slow.next.next; return dummyNode.next;
}
}

🔥 LeetCode 热题 HOT 100(1-10)的更多相关文章

  1. LeetCode 热题 HOT 100(05,正则表达式匹配)

    LeetCode 热题 HOT 100(05,正则表达式匹配) 不够优秀,发量尚多,千锤百炼,方可成佛. 算法的重要性不言而喻,无论你是研究者,还是最近比较火热的IT 打工人,都理应需要一定的算法能力 ...

  2. 🔥 LeetCode 热题 HOT 100(81-90)

    337. 打家劫舍 III 思路:后序遍历 + 动态规划 推荐题解:树形 dp 入门问题(理解「无后效性」和「后序遍历」) /** * Definition for a binary tree nod ...

  3. 🔥 LeetCode 热题 HOT 100(71-80)

    253. 会议室 II(NO) 279. 完全平方数 class Solution { public int numSquares(int n) { // dp[i] : 组成和为 i 的最少完全平方 ...

  4. 🔥 LeetCode 热题 HOT 100(51-60)

    142. 环形链表 II 思路:快慢指针,快慢指针相遇后,慢指针回到头,快慢指针步伐一致一起移动,相遇点即为入环点 /** * Definition for singly-linked list. * ...

  5. 🔥 LeetCode 热题 HOT 100(31-40)

    75. 颜色分类 思路:将 2 往后放,0 往前放,剩余的1自然就放好了. 使用双指针:left.right 分别指向待插入的 0 和 2 的位置,初始 left 指向数组头,right 指向数组尾部 ...

  6. 🔥 LeetCode 热题 HOT 100(21-30)

    46. 全排列 思路:典型回溯法 class Solution { public List<List<Integer>> permute(int[] nums) { Linke ...

  7. 🔥 LeetCode 热题 HOT 100(61-70)

    207. 课程表 思路:根据题意可知:当课程之间不存在 环状 循环依赖时,便能完成所有课程的学习,反之则不能.因此可以将问题转换成: 判断有向图中是否存在环.使用 拓扑排序法 : 构建 入度表:记录每 ...

  8. 🔥 LeetCode 热题 HOT 100(41-50)

    102. 二叉树的层序遍历 思路:使用队列. /** * Definition for a binary tree node. * public class TreeNode { * int val; ...

  9. 🔥 LeetCode 热题 HOT 100(11-20)

    20. 有效的括号 class Solution { public boolean isValid(String s) { Map<Character, Character> map = ...

随机推荐

  1. NOIP模拟测试28「阴阳·虎·山洞」

    写这几个题解我觉得我就像在按照官方题解抄一样 阴阳 题解 将题目中给的阴阳看作黑色和白色 首先我们观察到最后生成图中某种颜色必须是竖着单调递增或竖着单调递减 类似这样 否则不满足这个条件 但合法染色方 ...

  2. 美化terminal时碰到的问题- Set-Theme

    报错: 1 Set-Theme Set-Theme: The term 'Set-Theme' is not recognized as a name of a cmdlet, function, s ...

  3. MySQL基本sql语句总结

    目录 约束 表操作 查看表结构与修改表名 修改字段名与字段数据类型 添加与删除字段 修改字段的排列位置 删除表的外键约束 增删改 插入数据 更新数据 删除数据 查询 单表查询 连接查询 子查询 视图 ...

  4. Unity3D学习笔记2——绘制一个带纹理的面

    目录 1. 概述 2. 详论 2.1. 网格(Mesh) 2.1.1. 顶点 2.1.2. 顶点索引 2.2. 材质(Material) 2.2.1. 创建材质 2.2.2. 使用材质 2.3. 光照 ...

  5. 24、Keepalived高可用介绍

    24.1.什么是keepalived: Keepalived 软件起初是专为 LVS 负载均衡软件设计的,用来管理并监控 LVS 集群系统中各个服务节点的状态,后来又加入了可以实现高可用的 VRRP ...

  6. CosId 1.1.8 发布,通用、灵活、高性能的分布式 ID 生成器

    CosId 通用.灵活.高性能的分布式 ID 生成器 介绍 CosId 旨在提供通用.灵活.高性能的分布式 ID 生成器. 目前提供了三类 ID 生成器: SnowflakeId : 单机 TPS 性 ...

  7. angular组件间的通信(父子、不同组件的数据、方法的传递和调用)

    angular组件间的通信(父子.不同组件的数据.方法的传递和调用) 一.不同组件的传值(使用服务解决) 1.创建服务组件 不同组件相互传递,使用服务组件,比较方便,简单,容易.先将公共组件写在服务的 ...

  8. SpringCloud:feign默认jackson解析'yyyy-MM-ddTHH:mm:ssZ'时间格式报错

    Feign默认的使用jackson解析,所以时间传值时会报错,时间格式错误 解决办法: 修改feign解析方式为fastjson方式: @Configuration public class CxfC ...

  9. linux 退出状态码

    状态码 描述 0 命令成功结束 1 一般性未知错误 2 不适合的shell 命令 123 命令不可执行 127 没找到命令 128 无效退出参数 128+x 与linux信号x相关的严重错误 130 ...

  10. mysql中的条件语句case when/if函数

    主要知识点为case函数,if函数,ifnull函数,elt函数几部分,主要用于mysql语句中的逻辑判断 待操作的表如下: p.p1 { margin: 0; font: 16px Menlo; c ...