LeetCode01 两数之后
题目详情
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
提示:
2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
只会存在一个有效答案
进阶:你可以想出一个时间复杂度小于 O(n2) 的算法吗?
代码
public class LeetCode01 {
public static void main(String[] args) {
Solution solution = new Solution();
int[] nums = {7, 2, 12, 8, 15};
int target = 14;
System.out.print(Arrays.toString(solution.twoSum(nums, target)));
}
static class Solution {
public int[] twoSum1(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[j] == target - nums[i]) {
return new int[]{i, j};
}
}
}
throw new IllegalArgumentException("No two sum solution");
}
public int[] twoSumHash(int[] nums, int target) {
Map map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
map.put(nums[i], i);
}
Iterator it = map.keySet().iterator();
int key, value;
while (it.hasNext()) {
key = (int) it.next();
value = map.get(key);
int left = target - key;
if (map.containsKey(left) && value != map.get(left)) {
return new int[]{value, map.get(left)};
}
}
throw new IllegalArgumentException("No two sum solution");
}
public int[] twoSum2(int[] nums, int target) {
Map map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
map.put(nums[i], i);
}
for (int i = 0; i < nums.length; i++) {
int left = target - nums[i];
if (map.containsKey(left) && map.get(left) != i) {
return new int[]{i, map.get(left)};
}
}
throw new IllegalArgumentException("No two sum solution");
}
public int[] twoSum3(int[] nums, int target) {
Map map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int left = target - nums[i];
if (map.containsKey(left)) {
return new int[]{map.get(left), i};
}
map.put(nums[i], i);
}
throw new IllegalArgumentException("No two sum solution");
}
public int[] twoSum(int[] nums, int target) {
Map map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int key = target - nums[i];
if (map.containsKey(key)) {
return new int[]{i, map.get(key)};
}
map.put(nums[i], i);
}
throw new IllegalArgumentException("未找到");
}
}
}
LeetCode02 两数相加
题目详情
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例 1:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
示例 2:
输入:l1 = [0], l2 = [0]
输出:[0]
示例 3:
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]
提示:
每个链表中的节点数在范围 [1, 100] 内
0 <= Node.val <= 9
题目数据保证列表表示的数字不含前导零
代码
public class LeetCode02 {
public static void main(String[] args) {
Solution solution = new Solution();
ListNode node1 = ListNode.buildNode(new int[]{2, 4, 5});
ListNode node2 = ListNode.buildNode(new int[]{5, 6, 4});
ListNode.printNode(node1);
ListNode.printNode(node2);
ListNode.printNode(solution.addTwoNumbers(node1, node2));
ListNode.printNode(solution.addTwoNumbers2(node1, node2));
}
/*
a. 两条链表相加,返回的结果要求是链表,每次插值的时候都要在链表的尾端添加。
需要一个返回结果的链表res,需要一个指针,每次添加后都指向最后一个节点。
b. 如果有进数(两数想和大于十进1),下次相加时一起加上。
c. 两条链表长度可能不相等,因此结束循环后,需要判断是否有未遍历完的链表,有则单独遍历,没有则进入下一步。
d. 当两条链表都遍历结束,还需要判断进数是否为0,为0什么也不做,不为0在运行结果的链表尾部添加值为1的节点。
c. 最后可以直接返回res链表
*/
static class Solution {
public ListNode addTwoNumbers(ListNode p, ListNode q) {
ListNode dummyHead = new ListNode(0);
ListNode curr = dummyHead;
int carry = 0;
while (p != null || q != null) {
int x = (p != null) ? p.val : 0;
int y = (q != null) ? q.val : 0;
int sum = carry + x + y;
carry = sum / 10;
curr.next = new ListNode(sum % 10);
curr = curr.next;
if (p != null) p = p.next;
if (q != null) q = q.next;
}
if (carry > 0) {
curr.next = new ListNode(carry);
}
return dummyHead.next;
}
/**
* 用最常规的方法求解
* step1:遍历两个链表,两者之和为新的链表res,如果长度不一样,则默认为0
* step2:遍历新链表res,如果大于10则,下一个新加1
*/
public ListNode addTwoNumbers2(ListNode p, ListNode q) {
ListNode dummyHead = new ListNode(0);
//指针指到原来的位置
ListNode res = dummyHead;
while (p != null || q != null) {
int a = p != null ? p.val : 0;
int b = q != null ? q.val : 0;
res.next = new ListNode(a + b);
//移动到尾部结点
res = res.next;
if (p != null) p = p.next;
if (q != null) q = q.next;
}
//上面res指针已经到了,所以重新指回原来的位置
res = dummyHead;
int carry = 0;
while (res != null) {
//保存原来的值
int value = res.val;
//加上上个节点的carry
res.val = (carry + value) % 10;
//新的 carry
carry = (carry + value) / 10;
//链表末尾时且 carry>0
if (res.next == null && carry > 0) {
res.next = new ListNode(carry);
//carry已经利用过了,重新初始化
carry = 0;
}
if (res != null) res = res.next;
}
return dummyHead.next;
}
}
}
LeetCode03 无重复字符的最长子串
题目详情
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
提示:
0 <= s.length <= 5 * 104
s 由英文字母、数字、符号和空格组成
代码
public class LeetCode03 {
public static void main(String[] args) {
String s = "abcabcccbbefghgk";
System.out.println(new Solution().lengthOfLongestSubstring(s));
System.out.println(new Solution().lengthOfLongestSubstring2(s));
}
/*
我们使用两个指针表示字符串的某个子串的左右边界,其中左指针代表着上文中"枚举子串的起始位置",而右指针
在每一步的操作中,我们会将左指针向右移动一格,表示我们开始枚举下一个字符的起始位置,,然后我们可以不断地向右移动右指针,但需要保证这两个指针对应的子串中没有重复的字符。
在移动结束后,这个子串就对应着 以左指针开始,不包含重复字符的最长子串。我们记录下这个子串的长度。
在枚举结束后,我们找到最长的子串的长度即为答案。
*/
static class Solution {
public int lengthOfLongestSubstring(String s) {
// 哈希集合,记录每个字符是否出现过
Set set = new HashSet();
int n = s.length();
// 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
int right = -1, ans = 0;
for (int i = 0; i < n; ++i) {
if (i != 0) {
// 左指针向右移动一格,移除一个字符
set.remove(s.charAt(i - 1));
}
while (right + 1 < n && !set.contains(s.charAt(right + 1))) {
// 不断地移动右指针
set.add(s.charAt(right + 1));
++right;
}
// 第 i 到 right 个字符是一个极长的无重复字符子串
ans = Math.max(ans, right - i + 1);
}
return ans;
}
public int lengthOfLongestSubstring2(String s) {
int n = s.length(), ans = 0;
//创建map窗口,i为左区间 j为右区间 ,右边界移动
Map map = new HashMap<>();
for (int j = 0, i = 0; j < n; j++) {
//如果窗口中包含当前字符
if (map.containsKey(s.charAt(j))) {
//左边界移动到 相同字符的下一个位置和i当前位置中更靠右的位置
i = Math.max(map.get(s.charAt(j)), i);
}
//比对当前无重复字段长度和存储的长度,选最大值并替换
//j-i+1是因为此时i,j索引扔处于不重复的位置,j还没有向后移动
ans = Math.max(ans, j - i + 1);
//j+1为下一个字符位置
map.put(s.charAt(j), j + 1);
}
return ans;
}
}
}
LeetCode04寻找两个正序数组的中位数
题目详情
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。
示例 1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
提示:
nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
-106 <= nums1[i], nums2[i] <= 106
代码
public class LeetCode04 {
public static void main(String[] args) {
int[] A = {1, 2};
int[] B = {3, 4};
System.out.println(new Solution().findMedianSortedArrays(A, B));
System.out.println(new Solution().findMedianSortedArrays1(A, B));
}
static class Solution {
public double findMedianSortedArrays(int[] A, int[] B) {
int m = A.length;
int n = B.length;
if (m > n) { // to ensure m<=n
int[] temp = A;
A = B;
B = temp;
int tmp = m;
m = n;
n = tmp;
}
int iMin = 0, iMax = m, halfLen = (m + n + 1) / 2;
while (iMin <= iMax) {
int i = (iMin + iMax) / 2;
int j = halfLen - i;
if (i < iMax && B[j - 1] > A[i]) {
iMin = i + 1; // i 太小了
} else if (i > iMin && A[i - 1] > B[j]) {
iMax = i - 1; // i 太大了
} else { // i 刚刚好
int maxLeft = 0;
if (i == 0) {
maxLeft = B[j - 1];
} else if (j == 0) {
maxLeft = A[i - 1];
} else {
maxLeft = Math.max(A[i - 1], B[j - 1]);
}
if ((m + n) % 2 == 1) {
return maxLeft;
}
int minRight = 0;
if (i == m) {
minRight = B[j];
} else if (j == n) {
minRight = A[i];
} else {
minRight = Math.min(B[j], A[i]);
}
return (maxLeft + minRight) / 2.0;
}
}
return 0.0;
}
public double findMedianSortedArrays1(int[] A, int[] B) {
int leftLength = A.length;
int rightLength = B.length;
// 为了保证第一个数组比第二个数组小(或者相等)
if (leftLength > rightLength) {
return findMedianSortedArrays1(B, A);
}
// 分割线左边的所有元素需要满足的个数 m + (n - m + 1) / 2;
// 两个数组长度之和为偶数时,当在长度之和上+1时,由于整除是向下取整,所以不会改变结果
// 两个数组长度之和为奇数时,按照分割线的左边比右边多一个元素的要求,此时在长度之和上+1,就会被2整除,会在原来的数
//的基础上+1,于是多出来的那个1就是左边比右边多出来的一个元素
int totalLeft = (leftLength + rightLength + 1) / 2;
// 在 A 的区间 [0, leftLength] 里查找恰当的分割线,
// 使得 A[i - 1] <= B[j] && B[j - 1] <= A[i]
int left = 0;
int right = leftLength;
// A[i - 1] <= B[j]
// 此处要求第一个数组中分割线的左边的值 不大于(小于等于) 第二个数组中分割线的右边的值
// B[j - 1] <= A[i]
// 此处要求第二个数组中分割线的左边的值 不大于(小于等于) 第一个数组中分割线的右边的值
// 循环条件结束的条件为指针重合,即分割线已找到
while (left < right) {
// 二分查找,此处为取第一个数组中左右指针下标的中位数,决定起始位置
// 此处+1首先是为了不出现死循环,即left永远小于right的情况
// left和right最小差距是1,此时下面的计算结果如果不加1会出现i一直=left的情况,而+1之后i才会=right
// 于是在left=i的时候可以破坏循环条件,其次下标+1还会保证下标不会越界,因为+1之后向上取整,保证了
// i不会取到0值,即i-1不会小于0
// 此时i也代表着在一个数组中左边的元素的个数
int i = left + (right - left + 1) / 2;
// 第一个数组中左边的元素个数确定后,用左边元素的总和-第一个数组中元素的总和=第二个元素中左边的元素的总和
// 此时j就是第二个元素中左边的元素的个数
int j = totalLeft - i;
// 此处用了nums1[i - 1] <= B[j]的取反,当第一个数组中分割线的左边的值大于第二个数组中分割线的右边的值
// 说明又指针应该左移,即-1
if (A[i - 1] > B[j]) {
// 下一轮搜索的区间 [left, i - 1]
right = i - 1;
// 此时说明条件满足,应当将左指针右移到i的位置,至于为什么是右移,请看i的定义
} else {
// 下一轮搜索的区间 [i, right]
left = i;
}
}
// 退出循环时left一定等于right,所以此时等于left和right都可以
// 为什么left一定不会大于right?因为left=i。
// 此时i代表分割线在第一个数组中所在的位置
// A[i]为第一个数组中分割线右边的第一个值
// nums[i-1]即第一个数组中分割线左边的第一个值
int i = left;
// 此时j代表分割线在第二个数组中的位置
// B[j]为第一个数组中分割线右边的第一个值
// B[j-1]即第一个数组中分割线左边的第一个值
int j = totalLeft - i;
// 当i=0时,说明第一个数组分割线左边没有值,为了不影响
// A[i - 1] <= B[j] 和 Math.max(nums1LeftMax, nums2LeftMax)
// 的判断,所以将它设置为int的最小值
int nums1LeftMax = i == 0 ? Integer.MIN_VALUE : A[i - 1];
// 等i=第一个数组的长度时,说明第一个数组分割线右边没有值,为了不影响
// B[j - 1] <= A[i] 和 Math.min(nums1RightMin, nums2RightMin)
// 的判断,所以将它设置为int的最大值
int nums1RightMin = i == leftLength ? Integer.MAX_VALUE : A[i];
// 当j=0时,说明第二个数组分割线左边没有值,为了不影响
// B[j - 1] <= A[i] 和 Math.max(nums1LeftMax, nums2LeftMax)
// 的判断,所以将它设置为int的最小值
int nums2LeftMax = j == 0 ? Integer.MIN_VALUE : B[j - 1];
// 等j=第二个数组的长度时,说明第二个数组分割线右边没有值,为了不影响
// A[i - 1] <= B[j] 和 Math.min(nums1RightMin, nums2RightMin)
// 的判断,所以将它设置为int的最大值
int nums2RightMin = j == rightLength ? Integer.MAX_VALUE : B[j];
// 如果两个数组的长度之和为奇数,直接返回两个数组在分割线左边的最大值即可
if (((leftLength + rightLength) % 2) == 1) {
return Math.max(nums1LeftMax, nums2LeftMax);
} else {
// 如果两个数组的长度之和为偶数,返回的是两个数组在左边的最大值和两个数组在右边的最小值的和的二分之一
// 此处不能被向下取整,所以要强制转换为double类型
return (double) ((Math.max(nums1LeftMax, nums2LeftMax) + Math.min(nums1RightMin, nums2RightMin))) / 2;
}
}
}
}
LeetCode05最长的回文子串
题目详情
给你一个字符串 s,找到 s 中最长的回文子串。
示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd"
输出:"bb"
提示:
1 <= s.length <= 1000
s 仅由数字和英文字母组成
代码
public class LeetCode05 {
public static void main(String[] args) {
System.out.println(new Solution().longestPalindrome("babbad"));
System.out.println(new Solution().longestPalindrome2("babbad"));
System.out.println(new Solution().longestPalindrome3("babbad"));
}
static class Solution {
/*
暴力解法
*/
public String longestPalindrome(String s) {
int len = s.length();
if (len < 2) {
return s;
}
int maxLen = 1;
int begin = 0;
//s.charAt(i)每次都会检查数组下标越界,因此先转换成字符数组,这一步非必需
char[] charArray = s.toCharArray();
//枚举所有长度严格大约1的子串 charArray[i..j]
for (int i = 0; i < len - 1; i++) {
for (int j = i + 1; j < len; j++) {
if (j - i + 1 > maxLen && validPalindromic(charArray, i, j)) {
maxLen = j - i + 1;
begin = i;
}
}
}
return s.substring(begin, begin + maxLen);
}
private boolean validPalindromic(char[] charArray, int left, int right) {
while (left < right) {
if (charArray[left] != charArray[right]) {
return false;
}
left++;
right--;
}
return true;
}
/*
中心扩散法
- 枚举所有可能的回文子串的中心位置;
- 中心位置可能是一个字符,也有可能是两个相邻的字符
- 记录最长回文子串的相关变量
*/
public String longestPalindrome2(String s) {
if (s == null || s.length() < 1) return "";
int start = 0, end = 0;
for (int i = 0; i < s.length(); i++) {
int oddLen = expandAroundCenter(s, i, i);
int evenLen = expandAroundCenter(s, i, i + 1);
int len = Math.max(oddLen, evenLen);
if (len > end - start) {
start = i - (len - 1) / 2;
end = i + len / 2;
}
}
return s.substring(start, end + 1);
}
private int expandAroundCenter(String s, int left, int right) {
int L = left, R = right;
while (L >= 0 && R < s.length() && s.charAt(L) == s.charAt(R)) {
L--;
R++;
}
return R - L - 1;
}
/*
动态规划(回文的子串也是回文)
状态dp[i][j]表示子串s[i..j]是否为回文子串
得到状态转移方程:dp[i][j]=(s[i] == s[j]) and dp[i+1][j-1]
边界条件:j-1-(i+1)+1<2,整理得j-i<3 即 j-i+1<4 即s[i..j]长度为2或者3时,不用检查子串是否回文
动态规划,利用到了之前计算的值,所以相对快
初始化:dp[i][i] =true
输出:在得到一个状态的值为true的时候,记录起始位置和长度,填表完成以后再截取
状态规划就是填表,对角线肯定是true
状态转移方程:dp[i][j] = (s[i] == s[j]) and (j-i<3 or dp[i+1][j-1])
由于 dp[i][j]参考它左下方的值:
1,先升序填列
2,再升序填行
*/
public String longestPalindrome3(String s) {
int len = s.length();
if (len < 2) {
return s;
}
int maxLen = 1;
int begin = 0;
//dp[i][j] 表示s[i..j]是否是回文串
boolean[][] dp = new boolean[len][len];
for (int i = 0; i < len; i++) {
dp[i][i] = true;
}
char[] charArray = s.toCharArray();
//注意:左下角先填
for (int j = 1; j < len; j++) {
for (int i = 0; i < j; i++) {
if (charArray[i] != charArray[j]) {
dp[i][j] = false;
} else {
if (j - i < 3) {
dp[i][j] = true;
} else {
dp[i][j] = dp[i + 1][j - 1];
}
}
//只要dp[i][j]==true成立,就表示子串s[i..j]是回文,此时记录回文长度和起始位置
if (dp[i][j] && j - i + 1 > maxLen) {
maxLen = j - i + 1;
begin = i;
}
}
}
return s.substring(begin, begin + maxLen);
}
}
}
标题
题目详情
代码