LeetCode-按顺序刷题备注1-50
- 2.两数相加
- ``3.无重复字符的最长子串``
- 4.寻找正序数组的中位数
- ``5.最长回文子串``
- 6.字形变形
- 7.整数反转
- 8.字符串转换整数
- 11.盛最多水的容器
- 14.最长公共前缀
- 17.电话号码的字母组合
- ``18.四数之和``
- ``23.合并K的升序链表``
- ``31.下一个排列``
- ``31.最长有效括号``
- 30.串联所有单词的子串
- 33.搜索旋转排序数组
- 81.搜索旋转排序数组 II
- 34.在排序数组中查找元素的第一个和最后一个位置
- 36.有效的数独
- ``41.缺失的第一个正数``
- 剑指offer03:数组中重复的数字
- ``42.接雨水``
- 43.字符串相乘
- ``45. 跳跃游戏 II``
- 49.字母异位词分组
- ``50.Pow(x, n)``
2.两数相加
- 代码演示
/**
* 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 addTwoNumbers(ListNode l1, ListNode l2) {
ListNode pre = new ListNode(0);
ListNode cur = pre;
int isBig = 0;
while(l1 != null || l2 != null){
//对l1与l2分别进行处理,来判断其是否为空
int x1 = l1 == null ? 0 : l1.val;
int x2 = l2 == null ? 0 : l2.val;
int num = x1 + x2 + isBig;
isBig = num > 9 ? 1 : 0;
num = num % 10;
cur.next = new ListNode(num);
cur = cur.next;
/*
必须为l1!= null .而不是 l1.next != null。
假设当前条件为l1.next != null。 l1到了最后一个节点,l1.next为空,不能执行l1 = l1.next,l1则一直不为空,循环不会终止
同时,while循环条件为两者同时不为空,因此只需判断当前节点是否为空,来决定是否next
*/
if(l1 != null){
l1 = l1.next;
}
if(l2 != null){
l2 = l2.next;
}
}
if(isBig != 0){
cur.next = new ListNode(isBig);
}
return pre.next;
}
}
3.无重复字符的最长子串
方法1:map做重复值判断
- 代码演示:
class Solution {
/*
[start,end]:表示当前无重复子串
*/
public int lengthOfLongestSubstring(String s) {
int len = s.length();
if(len == 0){
return 0;
}
int res = 0;
Map<Character,Integer> map = new HashMap<>();
for(int end = 0,start = 0 ;end < len ; end++){
if(map.containsKey(s.charAt(end))){
/*
因为[start,end]表示当前无重复子串。因此取出来的start下标需要+1,也就是map.get(s.charAt(end)) + 1
同时,可能出现如下情况,如abccb。首先c重复,start = 3,b重复后,若不加max,则start变为2,有误
*/
start = Math.max(map.get(s.charAt(end)) + 1,start);
}
//因为每次end都要进行更新,因此每次需要对res进行更新。
res = Math.max(res,end - start + 1);
map.put(s.charAt(end),end);
}
return res;
}
}
方法2:滑动窗口,通过数组来替换map
- 代码演示
class Solution {
/*
flag数组:记录字符出现的次数;数组长度为256,对应其ASCII值。
值只可能是0或者1,第一次出现为0,发现重复后就可以去重。
*/
public int lengthOfLongestSubstring(String s) {
int[] flag = new int[256];
char[] sChar = s.toCharArray();
int len = sChar.length;
if(len == 0 ){
return 0;
}
int res = 0;
int start = 0;
int end = 0;
/*
若flag[sChar[end]] == 0,则说明当前字符不重复,则继续往后遍历
否则,则说明当前字符有重复,则flag[sChar[start]]--,将当前滑动窗口起始位置的字符标记减小,同时起始位置start++。
直至当前滑动窗口中没有重复元素。 这两步的操作,实际上为去除当前滑动窗口字符的重复值。
实际上,滑动窗口的大小是一直在变化中,且滑动窗口中的字符串是无重复元素的。
我们需要比较当前滑动窗口与以往的最大值来得到最终的滑动窗口最大值,
*/
while(end < len){
if(flag[sChar[end]] == 0){
flag[sChar[end]] ++;
end++;
}else{
flag[sChar[start]]--;
start++;
}
res = Math.max(res,end-start);
}
return res;
}
}
4.寻找正序数组的中位数
方法1:合并数组
- 代码演示:
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int len1 = nums1.length;
int len2 = nums2.length;
int len = len1+len2;
int[] res = new int[len];
if(len1 == 0 && len2 == 0){
return 0;
}
int c1 = 0;
int c2 = 0;
int rc = 0;
while(c1 < len1 && c2 < len2){
if(nums1[c1] < nums2[c2]){
res[rc++] = nums1[c1++];
}else{
res[rc++] = nums2[c2++];
}
}
while(c1 < len1){
res[rc++] = nums1[c1++];
}
while(c2 < len2){
res[rc++] = nums2[c2++];
}
return len % 2 == 0 ? (double)(res[len / 2] + res[len/2 - 1]) / 2 : res[len/2];
}
}
- 分析:时间复杂度为O(M+N)
方法2:二分查找,使时间复杂度为O(log(m+n))
- 代码演示:
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
//始终使得数组1的长度较小,方便后续处理
if(nums1.length > nums2.length){
int[] temp = nums1;
nums1 = nums2;
nums2 = temp;
}
int m = nums1.length;
int n = nums2.length;
/*
totalLeft:表示分割线左边的元素总和。
做了特殊处理,定义数组总长度为奇数时,我们使左边元素比右边元素个数多1,因此totalLeft可以用统一公式来计算。
*/
int totalLeft = (m+n+1)/2;
int left = 0;
int right = m;
/*
i:数组1的分界线,j:数组2的分界线。分界线的值等于分界线左边的值的个数,也就是说,分界线的值始终等于分界线右边第第一个值的下标。
通过二分查找,在数组1[0...m]中,找到符合“分界线左边的值始终小于分界线右边的值”的分界线位置。
因为两个数组都为有序数组,因此通过比较两个数组分界线旁值的大小,来确定分界线的移动。
最终找到一个合适的位置,使得 nums1[i - 1] <= nums2[j] && nums2[j - 1] <= nums1[i]
若数组1的分界线确定,则通过分界线左边的总值为一个定值,可确定数组2的分界线。
*/
while(left < right){
int i = left + (right - left + 1) / 2;
int j = totalLeft - i;
if(nums1[i-1] > nums2[j]){
right = i-1;
}else{
left = i;
}
}
int i = left;
int j = totalLeft - i;
/*
边界判断,分界线可能会在数组的边界。
因为要对分界线左边取最大值与右边取最小值,因此定义左边的边界情况,值最小,不影响输出时取最大。右边的边界状况同理分析。
*/
int nums1LeftMax = i == 0 ? Integer.MIN_VALUE : nums1[i - 1];
int nums1RightMin = i == m ? Integer.MAX_VALUE : nums1[i];
int nums2LeftMax = j == 0 ? Integer.MIN_VALUE : nums2[j -1];
int nums2RightMin = j == n ? Integer.MAX_VALUE : nums2[j];
if((m+n) % 2 == 1){
return Math.max(nums1LeftMax,nums2LeftMax);
}else{
return (double)((Math.max(nums1LeftMax,nums2LeftMax) + Math.min(nums1RightMin,nums2RightMin))) / 2;
}
}
}
方法3:归并思想
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int len1 = nums1.length;
int len2 = nums2.length;
int index = (len1 + len2) / 2; //目标索引,找到目标索引值即可停止。
int[] res = new int[index + 1];
int count = 0;
int index1 = 0;
int index2 = 0;
while(count < index + 1){
//越界判断
if(index1 == len1){
res[count++] = nums2[index2++];
continue;
}
if(index2 == len2){
res[count++] = nums1[index1++];
continue;
}
//比较判断
if(nums1[index1] < nums2[index2]){
res[count++] = nums1[index1++];
}else{
res[count++] = nums2[index2++];
}
}
//判断奇偶性进行输出
if((len1+ len2) % 2 == 1){
return (double)res[index];
}else{
return (double)(res[index] + res[index -1]) / 2;
}
}
}
5.最长回文子串
-题解: https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zhong-xin-kuo-san-dong-tai-gui-hua-by-liweiwei1419/
方法1:回溯,超时
- 思路:按照题目131的思路,运用递归的方式找出所有回文子串,通过比较选择最大的那个。但是,运行结果会超时。
- 代码演示:
class Solution {
int max = 0;
String res = "";
public String longestPalindrome(String s) {
int len = s.length();
if(len == 0){
return "";
}
if(len == 1){
return s;
}
dfs(s,0,len);
return res;
}
public void dfs(String s , int begin ,int len){
if(begin == len){
return;
}
for(int i = begin; i < len ;i++){
String substr = s.substring(begin,i + 1);
if(isHuiWen(substr)){
if(substr.length() > max){
res = substr;
max = substr.length();
}
dfs(s,i+1,len);
}
}
}
public boolean isHuiWen(String s){
int left = 0;
int right = s.length() -1;
while(left < right){
if(s.charAt(left) != s.charAt(right)){
return false;
}
left ++;
right --;
}
return true;
}
}
方法2:暴力解法
- 思路:通过双重循环遍历所有的字符串,返回长度最大的字符串。
- 代码演示:
class Solution {
/*
begin:记录最大长度回文串的起始地址
max:记录回文串的最大长度
通过begin与max,则可分割出最大长度的回文串。
*/
public String longestPalindrome(String s) {
int len = s.length();
int max = 1;
int begin = 0;
if(len == 0){
return "";
}
char[] sChar = s.toCharArray();
for(int i = 0; i < len;i++){
for(int j = i; j < len ; j++){
if(j-i+1 > max && isHuiWen(sChar,i,j)){
max = j - i + 1;
begin = i;
}
}
}
return s.substring(begin,begin+max);
}
public boolean isHuiWen(char[] sChar, int left, int right){
while(left < right){
if(sChar[left] != sChar[right]){
return false;
}
left ++;
right --;
}
return true;
}
}
方法3:DP
- 思路:一个字符串,如果字符串的两边相等,则字符串是否回文是由字符串去头去尾所得到的子字符串决定的。
- 代码演示:
class Solution {
//dp[i][j]代表字符串[i..j]是否为回文字符串
boolean[][] dp;
public String longestPalindrome(String s) {
int len = s.length();
if(len < 2){
return s;
}
this.dp = new boolean[len][len];
int max = 1;
int begin = 0;
//对角线上的元素肯定为回文字符串
for(int i = 0 ; i < len ;i++){
dp[i][i] = true;
}
/*
状态转移方程为dp[i][j] = (s[i]==s[j]) and (j - i < 3 or dp[i+1][j-1] )
由dp转移方程可以得到,在二维数组中。当前位置的状态与左下角位置的状态相关。
因此,先填充列,后填充行
同时,边界条件,j-1+(i+1)+1 < 2 => j-i < 3,当前字符串的长度为2或者3时,且两头字符相等时,不需要做缩减
*/
for(int j = 1 ; j < len ;j++){
for(int i = 0 ; i < j ;i++){
if(s.charAt(i) != s.charAt(j)){
dp[i][j] = false;
}else{
if(j - i < 3){
dp[i][j] = true;
}
if(dp[i+1][j-1]){
dp[i][j] = true;
}
}
//若当前子字符串为回文串,判断其长度并且距离最大长度以及起始位置
if(dp[i][j] && j -i + 1 > max){
max = j - i + 1;
begin = i;
}
}
}
return s.substring(begin,begin+max);
}
}
6.字形变形
方法1:
- 代码演示:
class Solution {
/*
由分析可知,Z字形字符串转为从左往右依次读取,那么需要将字符串受限存储为Z字形。
因为最终结果为从左往右依次读取,因此,字符存在具体的那一列不是很重要,只需要字符列的前后关系,也就是在每一行的前后关系,得出Z字形每一行的字符,最终进行拼接即可。
*/
public String convert(String s, int numRows) {
//若行数为0或者1,则不需要进行处理,直接输出即可
if(numRows < 2 || s.length() < 2){
return s;
}
int flag = -1;//表示方向是下还是上,1为下,-1为上
int row = 0; //遍历字符串时行的相应变化值
List<StringBuilder> list = new ArrayList<StringBuilder>();
//为每行创建一个新的StringBulder
for(int i = 0 ; i < numRows ;i++){
list.add(new StringBuilder());
}
//遍历挣个字符串,按行依次存储,到达首行与尾行,需要转变方向。将每一个字符拼接在相应的行字符串中即可。
for(char c : s.toCharArray()){
list.get(row).append(c);
if(row == 0 || row == numRows -1){
flag = -flag;
}
row += flag;
}
//按行遍历,拼接挣个结果
StringBuilder res = new StringBuilder();
for(StringBuilder sb : list){
res.append(sb);
}
return res.toString();
}
}
7.整数反转
- 思路:
- 如下图所示,整数的最大值为2147483647。在倒数第二位进行判断,也就是判断214748364。如果当前数值大于214748364,则说明溢出。如果等于214748364,则判断最后一位,如果大于7,则溢出。
- 如下图所示,整数的最大值为2147483647。在倒数第二位进行判断,也就是判断214748364。如果当前数值大于214748364,则说明溢出。如果等于214748364,则判断最后一位,如果大于7,则溢出。
- 最小值的情况如上类似。
- 代码演示:
class Solution {
int max = Integer.MAX_VALUE / 10;
int min = Integer.MIN_VALUE / 10;
public int reverse(int x) {
if(x > -10 && x < 10){
return x;
}
int res = 0;
int num = 0;
while(x != 0){
num = x % 10;
x = x / 10;
//判断最大
if(res > max || (res == max && num > 7)){
return 0;
}
//判断最小
if(res < min || (res == min && num < -8)){
return 0;
}
res = res * 10 + num;
}
return res;
}
}
8.字符串转换整数
- 注意点:有效字符串的要求
- 依据题目所给思路来进行编码。
- 代码演示:
public class Solution {
public int myAtoi(String str) {
int len = str.length();
// str.charAt(i) 方法回去检查下标的合法性,一般先转换成字符数组
char[] charArray = str.toCharArray();
// 1、去除前导空格
int index = 0;
while (index < len && charArray[index] == ' ') {
index++;
}
// 2、如果已经遍历完成(针对极端用例 " ")
if (index == len) {
return 0;
}
// 3、如果出现符号字符,仅第 1 个有效,并记录正负
int sign = 1;
char firstChar = charArray[index];
if (firstChar == '+') {
index++;
} else if (firstChar == '-') {
index++;
sign = -1;
}
// 4、将后续出现的数字字符进行转换
// 不能使用 long 类型,这是题目说的
int res = 0;
while (index < len) {
char currChar = charArray[index];
// 4.1 先判断不合法的情况,因为
if (currChar > '9' || currChar < '0') {
break;
}
// 题目中说:环境只能存储 32 位大小的有符号整数,因此,需要提前判:断乘以 10 以后是否越界
if (res > Integer.MAX_VALUE / 10 || (res == Integer.MAX_VALUE / 10 && (currChar - '0') > Integer.MAX_VALUE % 10)) {
return Integer.MAX_VALUE;
}
if (res < Integer.MIN_VALUE / 10 || (res == Integer.MIN_VALUE / 10 && (currChar - '0') > -(Integer.MIN_VALUE % 10))) {
return Integer.MIN_VALUE;
}
// 4.2 合法的情况下,才考虑转换,每一步都把符号位乘进去
res = res * 10 + sign * (currChar - '0');
index++;
}
return res;
}
}
11.盛最多水的容器
- 代码演示:
class Solution {
public int maxArea(int[] height) {
int left = 0;
int right = height.length - 1;
int max = 0;
/*
因为从两边往中心寻找面积最大的过程中,长始终是减小的。因此,只有当宽增大时,取得的面积才有可能比之前的面积大
因此,对左右的宽进行比较,让小的一边改变。
*/
while(left < right){
int h = Math.min(height[left],height[right]);
max = Math.max(h * (right - left) , max);
if(height[left] < height[right]){
left++;
}else{
right--;
}
}
return max;
}
}
14.最长公共前缀
- 代码演示:
class Solution {
public String longestCommonPrefix(String[] strs) {
int strlen = strs.length;
if(strlen == 0){
return "";
}
int minlen = Integer.MAX_VALUE;
//找出字符串的最小长度
for(int i = 0 ; i < strlen ; i++){
minlen = Math.min(minlen,strs[i].length());
}
StringBuilder sb = new StringBuilder();
/*
外循环:字符遍历;内循环:字符串数组遍历
原理就是每次取一个字符,来依次在字符串数组中判断当前字符串的字符是否与其相等。
*/
for(int i = 0; i < minlen;i++){
char c = strs[0].charAt(i);
for(int j = 1 ; j < strlen ;j++){
if(strs[j].charAt(i) != c){
return sb.toString();
}
}
sb.append(c);
}
return sb.toString();
}
}
17.电话号码的字母组合
- 思路:递归
- 代码演示
class Solution {
String[] chars = new String[]{"abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
int len;
List<String> res = new ArrayList<>();
public List<String> letterCombinations(String digits) {
this.len = digits.length();
if(digits.length() == 0){
return res;
}
StringBuilder sb = new StringBuilder();
dfs(digits,0,sb);
return res;
}
/*
c:为当前的遍历数字;str:取出当前数字所对应的字符串
遍历当前字符串的字符,进行递归处理。
输出:直至拼接的位置index==len,说明当前字符串拼接完成
*/
public void dfs(String digits,int index,StringBuilder sb){
if(index == len){
res.add(sb.toString());
return;
}
char c = digits.charAt(index);
String str = chars[c-'0'-2];
for(int i = 0 ; i < str.length() ; i++){
dfs(digits,index+1,sb.append(str.charAt(i)));
sb.deleteCharAt(sb.length()-1);
}
}
}
18.四数之和
- 思路:与三数之和类似,只是我们当前进行双重循环。固定两个数值,从而在[left,right]中寻找合适的数值,使得nums[i] + nums[j] + nums[left] + nums[right] == target。
- 代码演示:
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> fourSum(int[] nums, int target) {
int len = nums.length;
if(len == 0){
return res;
}
Arrays.sort(nums);
for(int i = 0; i < len - 3 ;i++){
//去重操作
if(i > 0 && nums[i] == nums[i-1]) continue;
/*
若当前[i..len-1]中前四个数值加起来大于target,因为整个数组递增,则可判断此序列中不会有等于target的结果,执行break
若当前[i..len-1]中,固定nums[i],且与最后三个数值的和小于target,则当前nums[i]过于小,直接continue跳出当前循环
*/
if(target < nums[i] + nums[i+1] + nums[i+2] + nums[i+3]) break;
if(target > nums[i] + nums[len-1] + nums[len -2] + nums[len -3]) continue;
for(int j = i + 1 ; j <len-2 ;j++){
//去重
if(j > i+1 && nums[j] == nums[j-1]) continue;
/*
与i的剪枝操作类似,只是此时需要固定nums[i],从而判断当前nums[j]的相应操作数值大小
*/
if(target < nums[i] + nums[j] + nums[j+1] + nums[j+2]) break;
if(target > nums[i] + nums[j] + nums[len-1] + nums[len -2]) continue;
int left = j+1;
int right = len-1;
while(left < right){
int sum = nums[i] + nums[j] + nums[left] + nums[right];
if(sum == target){
res.add(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(sum < target){
left++;
}else{
right--;
}
}
}
}
return res;
}
}
23.合并K的升序链表
方法1:归并排序思想
- 代码演示
/**
* 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 mergeKLists(ListNode[] lists) {
int len = lists.length;
if(len == 0 || lists == null){
return null;
}
return merge(lists,0,len-1);
}
public ListNode merge(ListNode[] lists,int left ,int right){
if(left == right) return lists[left];
int mid = (left + right + 1) / 2;
//选择两个链表
ListNode leftNode = merge(lists,left,mid-1);
ListNode rightNode = merge(lists,mid,right);
//合并
return mergeList(leftNode,rightNode);
}
public ListNode mergeList(ListNode l1 ,ListNode l2){
ListNode sentry = new ListNode(0);
ListNode pre = sentry;
while(l1 != null && l2 != null){
if(l1.val < l2.val){
pre.next = l1;
l1 = l1.next;
}else{
pre.next = l2;
l2 = l2.next;
}
pre = pre.next;
}
if(l1 != null){
pre.next = l1;
}
if(l2 != null){
pre.next = l2;
}
return sentry.next;
}
}
31.下一个排列
- 题解:https://leetcode-cn.com/problems/next-permutation/solution/xia-yi-ge-pai-lie-suan-fa-xiang-jie-si-lu-tui-dao-/
- 代码演示:
class Solution {
public void nextPermutation(int[] nums) {
int len = nums.length;
if(len == 1){
return;
}
int min = -1;
int max = -1;
/*
例如:14231。
首先从尾找到第一个相邻的递减序列,如23,min=2。
再从尾找到第一个比nums[min]=2大的数,也就是3。交换得14321。这样既可保证[i-1...len-1]为一个递减序列。
因为nums[i-1]已增大,所以整个数组排列增大。
因此,想要使得此排列为增大最小。翻转[i...len-1],使得[i...len-1]为一个递增序列。这样,则得到最小值。14312
*/
for(int i = len - 1 ; i >= 1 ;i--){
if(nums[i-1] < nums[i]){
min = i-1;
break;
}
}
//特殊情况,数组已然最大
if(min == -1){
reverseNums(nums,0);
return;
}
for(int i = len-1 ;i > min; i--){
if(nums[i] > nums[min]){
max = i;
break;
}
}
change(nums,min,max);
reverseNums(nums,min+1);
return;
}
public void change(int[] nums,int left ,int right){
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
public void reverseNums(int[] nums,int start){
int left = start;
int right = nums.length-1;
while(left < right){
change(nums,left,right);
left++;
right--;
}
}
}
31.最长有效括号
- 题解:官方题解+题解评论
方法1:DP
- 代码演示
class Solution {
public int longestValidParentheses(String s) {
int len = s.length();
if(len == 0){
return 0;
}
//dp[i] 表示以当前下标i为结尾的字符串匹配的最大长度
int[] dp = new int[len+1];
dp[0] = 0;
char[] schar = s.toCharArray();
int max = 0;
for(int i = 1 ; i < len ; i++){
if(schar[i] == '('){
dp[i] = 0;
}
/*
()((())
碰到')',首先寻找最后一个未匹配位置。
dp[i-1] == 0,说明i-1位置未匹配,pre=i-1-predp=i-1。
dp[i-1] != 0,则predp存储的为当前匹配的长度则,pre = i-1 - predp,寻找到未匹配位置
假设当前i=6,dp[i-1]=2,则pre= i-1-2=3,寻找到了当前未匹配位置
*/
if(schar[i] == ')'){
int predp = dp[i-1];
int pre = i - 1 - predp;
//进行匹配
if(pre >= 0 && schar[pre] == '('){
dp[i] = dp[i-1]+2;
/*
判断匹配位置的前一个位置是否还有有效匹配。
若有,则说明匹配序列连续,则加上前一个位置的dp。
若无,则+0.因为前一个位置无匹配,则dp[pre-1] = 0,因此直接加dp[pre-1]即可。
*/
if(pre - 1 >= 0){
dp[i] += dp[pre-1];
}
}
}
max = Math.max(dp[i],max);
}
return max;
}
}
方法2:栈
- 思路:对于字符串的匹配问题,一般情况都可以运用栈来解决
- 代码演示
class Solution {
public int longestValidParentheses(String s) {
int len = s.length();
if(len == 0){
return 0;
}
Deque<Integer> stack = new LinkedList<>();
char[] schar = s.toCharArray();
int max = 0;
/*
始终保持栈底元素是最后一个未匹配的右括号的下标。
为了方便处理,保证第一个元素为右括号时,栈为空不能执行pop,因此初始时push(-1)。
*/
stack.push(-1);
for(int i = 0; i < len ;i++){
if(schar[i] == '('){
stack.push(i);
}else{
stack.pop();
//如果当前栈为空,说明当前右括号未匹配,下标压入
//否则说明当前右括号有匹配,则当前i减去未匹配右括号的下标(栈底元素),则为长度
if(stack.size() == 0){
stack.push(i);
}else{
max = Math.max(max,i - stack.peek());
}
}
}
return max;
}
}
30.串联所有单词的子串
- 题解:https://leetcode-cn.com/problems/substring-with-concatenation-of-all-words/solution/chuan-lian-suo-you-dan-ci-de-zi-chuan-by-powcai/
方法1:暴力遍历
- 代码演示1:
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> res = new ArrayList<>();
Map<String,Integer> map = new HashMap<>();
//所给条件,words中每个字符串的长度相等
int oneLen = words[0].length();
int allLen = oneLen * words.length;
//map:存储的words字符串键值对,key:当前字符串;value:当前单词出现的次数
for(int i = 0;i < words.length;i++){
map.put(words[i],map.getOrDefault(words[i],0) + 1);
}
/*
每次从s中截取总长度的字符串,将此字符串依次进行分割。
首先判断字符串是否合理,不合理(words中没有此字符串),直接跳出。
合理,则压入tempMap,最终比较两个map是否相等即可。
*/
for(int i = 0 ; i < s.length() - allLen + 1 ;i++){
String substr = s.substring(i,i + allLen);
Map<String,Integer> tempMap = new HashMap<>();
for(int j = 0 ; j < allLen; j += oneLen){
String ss = substr.substring(j, j + oneLen);
if(!map.containsKey(ss)){
break;
}
tempMap.put(ss,tempMap.getOrDefault(ss,0) + 1);
}
if(map.equals(tempMap)){
res.add(i);
}
}
return res;
}
}
- 代码演示2:与代码1的思路一致,只是写法不同。
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> res = new ArrayList<>();
Map<String,Integer> map = new HashMap<>();
//所给条件,words中每个字符串的长度相等
int oneLen = words[0].length();
int allLen = oneLen * words.length;
//map:存储的words字符串键值对,key:当前字符串;value:当前单词出现的次数
for(int i = 0;i < words.length;i++){
map.put(words[i],map.getOrDefault(words[i],0) + 1);
}
/*
每次从s中截取总长度的字符串,将此字符串依次进行分割。
首先判断字符串是否合理,不合理(words中没有此字符串),直接跳出。
合理,则将tempMap中的字符串key的value-1。
如果value == 0,则说明words中的key字符串已匹配完,移除此key。
最终,tempMapsize == 0,则说明全部匹配成功
*/
for(int i = 0 ; i < s.length() - allLen + 1 ;i++){
String substr = s.substring(i,i + allLen);
//必须这样写。若直接用map进行判断,则每一次循环都是用的同一个map对象。
//这样写,每一次循环都是用的tempMap,只是将map的值赋值给tempMap
Map<String,Integer> tempMap = new HashMap<>(map);;
for(int j = 0 ; j < allLen; j += oneLen){
String ss = substr.substring(j, j + oneLen);
if(!tempMap.containsKey(ss)){
break;
}
tempMap.put(ss,tempMap.get(ss)-1);
if(tempMap.get(ss) == 0){
tempMap.remove(ss);
}
}
if(tempMap.size() == 0){
res.add(i);
}
}
return res;
}
}
方法2:滑动窗口 暂留
33.搜索旋转排序数组
二分法:
- 题解:二分法总结(35题):https://leetcode-cn.com/problems/search-insert-position/solution/te-bie-hao-yong-de-er-fen-cha-fa-fa-mo-ban-python-/
- 代码演示
class Solution {
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length-1;
//二分法
while(left < right){
int mid = left + (right- left + 1) /2;
if(nums[mid] == target){
return mid;
}
//在mid的两边,肯定有一边是有序的。
//在中间元素小于有边界元素时,[mid,right]肯定是有序的。否则[left,mid-1]区间肯定是有序的、
if(nums[mid] < nums[right]){
//通过此判断条件,说明target在[left,right]的右半部分
if(nums[mid] <= target && target <= nums[right]){
left = mid;
}else{
right = mid-1;
}
}else{
//通过此判断条件,说明target在[left,right]的左半部分
if(nums[left] <= target && target <= nums[mid-1]){
right = mid-1;
}else{
left = mid;
}
}
}
//因为mid=left+(right -left + 1) /2;因此将数组分为[left,mid-1],[mid,right]两部分。
//如果数组长度为1.mid=1,[mid,left] == null,因此需要进行特殊判断
if(nums[left] == target){
return left;
}
return -1;
}
}
81.搜索旋转排序数组 II
方法1:与右比较
- 代码演示:
class Solution {
public boolean search(int[] nums, int target) {
int len = nums.length;
int left = 0;
int right = len - 1;
while(left < right){
int mid = left + (right - left + 1) / 2;
if(nums[mid] == target){
return true;
}
/*
当数组为10111时,此时nums[mid]与nums[right]都是1,无法判断当前数组哪一边有序,因此需要去重。
去重,因为后面mid与right进行比较,因此对右边进行去重,直至nums[mid]与nums[right]不相等。
*/
if(nums[mid] == nums[right]){
right--;
continue;
}
//[mid,right]单调不减
if(nums[mid] < nums[right]){
if(nums[mid] <= target && target <= nums[right]){
left = mid;
}else{
right = mid - 1;
}
}else{
//[left,mid]单调不增
if(nums[mid] >= target && target >= nums[left]){
right = mid - 1;
}else{
left = mid;
}
}
}
if(nums[left] == target){
return true;
}
return false;
}
}
方法2:与左比较
- 思路:与方法1类似,只是比较的处理不同。
- 代码演示:
class Solution {
public boolean search(int[] nums, int target) {
int len = nums.length;
int left = 0;
int right = len - 1;
while(left < right){
int mid = left + (right - left) / 2;
if(nums[mid] == target){
return true;
}
if(nums[mid] == nums[left]){
left++;
continue;
}
if(nums[mid] > nums[left]){
if(nums[left] <= target && target <= nums[mid]){
right = mid -1;
}else{
left = mid;
}
}else{
if(nums[mid] < target && target <= nums[right]){
left = mid;
}else{
right = mid-1;
}
}
}
if(nums[left] == target){
return true;
}
return false;
}
}
34.在排序数组中查找元素的第一个和最后一个位置
二分法:
- 代码演示:
class Solution {
int len;
public int[] searchRange(int[] nums, int target) {
this.len = nums.length;
if(len == 0 || nums[len-1] < target){
return new int[]{-1,-1};
}
/*。
first:将区间分为[left,mid],[mid+1,right]。因为mid==target时,起始位置只可能在mid之前(包括mid),往前找。
last:将区间分为[left,mid-1],[mid,right]。因此mid==target时,结束位置只可能在mid之后(包括mid),往后找
*/
int first = findFirst(nums,target);
//若first未找到,则说明数组中无目标值,直接返回
if(first == -1){
return new int[]{-1,-1};
}
int last = findLast(nums,target);
return new int[]{first,last};
}
public int findFirst(int[] nums,int target){
int left = 0;
int right = len -1;
while(left < right){
int mid = left + (right - left) /2;
if(nums[mid] < target){
left = mid + 1;
}else if(nums[mid] > target){
right = mid-1;
}else{
right = mid;
}
}
//判断是否找到,last不用判断,因为若first可找到,last肯定会找到。极端情况,first==last
if(nums[left] == target){
return left;
}
return -1;
}
public int findLast(int[] nums,int target){
int left = 0;
int right = len -1;
while(left < right){
int mid = left + (right - left + 1) /2;
if(nums[mid] < target){
left = mid + 1;
}else if(nums[mid] > target){
right = mid - 1;
}else{
left = mid;
}
}
return left;
}
}
36.有效的数独
- 代码演示
class Solution {
public boolean isValidSudoku(char[][] board) {
boolean[][] rowF = new boolean[9][9];//行标记,判断每一行是否有重复元素
boolean[][] colF = new boolean[9][9];//列标记,判断每一列是否有重复元素
boolean[][] boxF = new boolean[9][9];//box标记,判断每一个box是否有重复元素
for(int i = 0 ; i < 9 ; i++){
for(int j = 0 ; j < 9 ; j++){
if(board[i][j] == '.'){
continue;
}
//将1-9字符转换为0-8下标
int num = board[i][j] - '0' - 1;
if(rowF[i][num]){
return false;
}else{
rowF[i][num] = true;
}
if(colF[j][num]){
return false;
}else{
colF[j][num] = true;
}
//box下标转换。算出当前[i][j]属于第几个box
int boxIndex = (i / 3) * 3 + j / 3;
if(boxF[boxIndex][num]){
return false;
}else{
boxF[boxIndex][num] = true;
}
}
}
return true;
}
}
41.缺失的第一个正数
方法1:暴力
- 空间复杂度为O(N),题目的进阶要求为常数级别
- 代码演示
class Solution {
public int firstMissingPositive(int[] nums) {
int len = nums.length;
if(len == 0){
return 1;
}
Set<Integer> set = new HashSet<>();
int max = 0;
//将正数存入set并记录最大值
for(int i = 0 ; i < nums.length;i++){
if(nums[i] > 0){
set.add(nums[i]);
max = Math.max(max,nums[i]);
}
}
//从1开始遍历到最大值,判断set中是否包含此值
for(int i = 1 ; i <= max ; i++){
if(!set.contains(i)){
return i;
}
}
//特殊情况,当数组的最大值为0时,或者数组为一个正数连续递增序列。
//此时寻找位置为0+1,max+1。max初值为0,因此return max+1;
return max +1;
}
}
- 优化思路:取消max,用数组长度来进行遍历。
- 对于一个长度为n的数组,其中没有出现的最小正正数只可能在[1…n+1]中。
class Solution {
public int firstMissingPositive(int[] nums) {
int len = nums.length;
if(len == 0){
return 1;
}
Set<Integer> set = new HashSet<>();
for(int i = 0 ; i < nums.length;i++){
if(nums[i] > 0){
set.add(nums[i]);
}
}
//只需判断当前小于或者数组长度的字符是否在数组中。
//因为若数组中出现负数,肯定缺失小于数组长度的正数。
//若数组从1开始连续递增,则返回len+1,返回数组长度的后一位。
for(int i = 1 ; i <= len ; i++){
if(!set.contains(i)){
return i;
}
}
return len + 1;
}
}
方法2:进阶要求
- 思路:原地哈希
- 代码演示:
class Solution {
public int firstMissingPositive(int[] nums) {
int len = nums.length;
if(len == 0){
return 1;
}
for(int i = 0 ; i < len ; i++){
//将nums[i]放入nums[i] - 1的位置上。也就是将i位置与nums[i]-1位置的数值进行交换
//因为长度为n的数组,未出现的最小正整数的只可能在[1...n+1]中,判断条件如下。
//正数 && 满足大小 && 去除重复
while(nums[i] > 0 && nums[i] <= len && nums[nums[i] - 1] != nums[i]){
swap(nums,nums[i] - 1,i);
}
}
for(int i = 0 ; i < len ;i ++){
if(nums[i] - 1 != i){
return i + 1;
}
}
return len + 1;
}
public void swap(int[] nums,int left, int right){
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
}
剑指offer03:数组中重复的数字
方法1:原地哈希
- 思路:在当前数字放在以当前数字为下标的位置上。如果碰到重复,则返回,否则交换。
- 代码演示
class Solution {
public int findRepeatNumber(int[] nums) {
int len = nums.length;
for(int i = 0 ; i < len ; i++){
//当当前数字不在位置上时,判断重复且交换。
//用while重复判断一直交换,因为每次换来的nums[nums[i]]可能还不在合适的位置。
while(nums[i] != i){
if(nums[i] == nums[nums[i]]){
return nums[i];
}
swap(nums,nums[i],i);
}
}
return -1;
}
public void swap(int[] nums,int left, int right){
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
}
42.接雨水
- 题解:
- https://leetcode-cn.com/problems/trapping-rain-water/solution/dan-diao-zhan-jie-jue-jie-yu-shui-wen-ti-by-sweeti/
方法1:栈
- 代码演示:
class Solution {
public int trap(int[] height) {
int len = height.length;
if(len == 0){
return 0;
}
//存储的为height的下标,stack存储的一直为单调不增序列
Deque<Integer> stack = new LinkedList<>();
int res = 0;
for(int i = 0 ; i < len ; i++){
//因为stack为单调不增。当栈不为空,且栈顶元素小于当前元素,说明数组有可能有“坑”。
//(两种情况[2,2,3],[4,2,3])。[2,2,3]这种情况处理重复元素时已经删除,[4,2,3]则说明有坑。
while(!stack.isEmpty() && height[i] > height[stack.peek()]){
//栈顶元素出栈,记录上次的处理的深度索引,且如去除重复深度索引
int oldIndex = stack.pop();
while(!stack.isEmpty() && height[stack.peek()] == height[oldIndex]){
stack.pop();
}
if(!stack.isEmpty()){
//记录坑的左高度的索引
int stackTop = stack.peek();
//与坑的右高度取最小值,且减去上次处理的深度,就为当前填充区域的深度。
//i-stackTop-1为宽度。[若]
res += (Math.min(height[i],height[stackTop]) - height[oldIndex]) * (i - stackTop - 1);
}
}
stack.push(i);
}
return res;
}
}
方法2:暴力解法
- 代码演示:
class Solution {
public int trap(int[] height) {
int len = height.length;
if(len == 0){
return 0;
}
int res = 0;
/*
依次遍历height数组,找到每一个数组的坐标最大值与右边最大值。
最终两者取最小减去当前值,就是在当前下标i下所能接的雨水
*/
for(int i = 1 ; i < len ; i++){
int leftMax = 0;
int rightMax = 0;
for(int j = i ; j >=0 ; j--){
leftMax = Math.max(leftMax,height[j]);
}
for(int j = i ; j < len ;j++){
rightMax = Math.max(rightMax,height[j]);
}
res += Math.min(leftMax,rightMax) - height[i];
}
return res;
}
}
方法3:DP
- 思路:因为在暴力解法中,每次都需要搜索左边与右边的最大值,消耗时间较长。因此,可以提前将最大值求出,分别求出从左到右与从右到左的最大值数组。在遍历期间就不需要遍历求解,直接寻找对应下标的最大值即可。
- 求最大值数组采用的为动态规划的思想。
- 代码演示:
class Solution {
public int trap(int[] height) {
int len = height.length;
if(len == 0){
return 0;
}
int[] leftMax = new int[len];
int[] rightMax = new int[len];
int res = 0;
leftMax[0] = height[0];
//从左往右
for(int i = 1 ; i < len ; i++){
leftMax[i] = Math.max(leftMax[i-1],height[i]);
}
rightMax[len-1] = height[len-1];
//从右到左
for(int i = len-2; i >= 0 ;i--){
rightMax[i] = Math.max(rightMax[i+1],height[i]);
}
//求每个下标的雨水值
for(int i = 0 ; i < len ; i++){
res += Math.min(rightMax[i],leftMax[i]) - height[i];
}
return res;
}
}
方法4:双指针
-
代码演示
class Solution {
public int trap(int[] height) {
int len = height.length;
if(len == 0){
return 0;
}
int left = 0;
int right = len - 1;
int leftMax = 0;//记录从左边开始的最大值
int rightMax = 0;//记录从右边开始的最大值
int res = 0;
while(left < right){
//右边柱子更高
if(height[left] < height[right]){
//积水的深度由左边柱子高度决定。更新最大值且求积水
if(height[left] >= leftMax){
leftMax = height[left];
}else{
res += (leftMax - height[left]);
}
left++;
}else{
//左边柱子更高,积水的深度由右边柱子高度决定。更新最大值且求积水
if(height[right] >= rightMax){
rightMax = height[right];
}else{
res += (rightMax - height[right]);
}
right--;
}
}
return res;
}
}
43.字符串相乘
- 思路:按照普通的乘法过程来逐位计算
- 代码演示:
class Solution {
public String multiply(String num1, String num2) {
if(num1.length() == 0 || num2.length() == 0){
return "";
}
if(num1.equals("0") || num2.equals("0")){
return "0";
}
if(num1.length() < num2.length()){
String s = num1;
num1 = num2;
num2 = s;
}
String res = "";
for(int i = num2.length()-1 ; i >= 0 ; i--){
StringBuilder temp = new StringBuilder();
//末尾补0
for (int j = num2.length() - 1; j > i; j--) {
temp.append(0);
}
int count = 0;
for(int j = num1.length()-1 ; j >= 0 ; j--){
int x = (num2.charAt(i) - '0') * (num1.charAt(j) - '0') + count;
count = x / 10;
x = x % 10;
temp.insert(0,x);
}
//对进位进行特殊处理,最终还有进位,则继续压入
if(count != 0){
temp.insert(0,count );
}
res = addString(res,new String(temp));
}
return res;
}
public String addString(String s1,String s2){
int index1 = s1.length()-1;
int index2 = s2.length()-1;
StringBuilder res = new StringBuilder();
int count = 0;
while(index1 >=0 || index2 >= 0){
int x1 = index1 >= 0 ? s1.charAt(index1) - '0' : 0;
int x2 = index2 >= 0 ? s2.charAt(index2) - '0' : 0;
int addNumber = x1 + x2 + count;
count = addNumber / 10;
addNumber = addNumber % 10;
res.insert(0,addNumber);
index1--;
index2--;
}
//对进位进行特殊处理,最终还有进位,则继续压入
if(count != 0){
res.insert(0,count);
}
return res.toString();
}
}
45. 跳跃游戏 II
- 题解:https://leetcode-cn.com/problems/jump-game-ii/solution/45-tiao-yue-you-xi-iiliang-chong-tan-xin-q48k/
方法1:
- 记录当前步与下一步的位置,进行判断
- 代码演示
class Solution {
/*
curIndex:当前步可到达的最大位置
nextIndex:下一步可到达的最大位置
res:跳数
*/
public int jump(int[] nums) {
int curIndex = 0;
int nextIndex = 0;
int res = 0;
for(int i = 0; i < nums.length; i++){
//记录下一步所能到达最大位置
nextIndex = Math.max(nextIndex,i+nums[i]);
if(i == curIndex){
//若已到达当前步最大位置且最大位置在末尾,跳出直接返回
if(curIndex == nums.length - 1){
break;
}else{
//否则更新步数,且更新当前步最大位置
res++;
curIndex = nextIndex;
}
}
}
return res;
}
}
方法2:方法1优化
class Solution {
/*
curIndex:当前步可到达的最大位置
nextIndex:下一步可到达的最大位置
res:跳数
*/
public int jump(int[] nums) {
int curIndex = 0;
int nextIndex = 0;
int res = 0;
//最多只能遍历到末尾位置的上一个位置
for(int i = 0; i < nums.length-1; i++){
//记录下一步所能到达最大位置
nextIndex = Math.max(nextIndex,i+nums[i]);
//i已经到达末尾位置的上一个位置,不需要进行再进行其余特殊判断,直接加1即可到达末尾位置。
if(i == curIndex){
//否则更新步数,且更新当前步最大位置
res++;
curIndex = nextIndex;
}
}
return res;
}
}
方法3:DP
- 思路:https://leetcode-cn.com/problems/jump-game-ii/solution/xiang-jie-dp-tan-xin-shuang-zhi-zhen-jie-roh4/
- 代码演示:
class Solution {
public int jump(int[] nums) {
//dp[i]表示最少经过几步可以到达i
int[] dp = new int[nums.length];
dp[0] = 0;
int j = 0;
//外循环:记录dp数组。内循环:记录可以经过一跳到达dp位置的最远位置。
for(int i = 1 ; i < nums.length ;i++){
while(j + nums[j] < i){
j++;
}
dp[i] = dp[j] + 1;
}
return dp[nums.length-1];
}
}
49.字母异位词分组
- 本题重点:如何将不同顺序字符串进行匹配。
方法1:排序
- 代码演示
class Solution {
/* 排序
本题一个重点:如何将不同顺序字符串进行匹配。
因此将不同顺序字符串转为字符数组,从而将字符数组排序,因此不同顺序字符串就可以进行匹配。
*/
public List<List<String>> groupAnagrams(String[] strs) {
int len = strs.length;
if(len == 0){
return new ArrayList<>();
}
Map<String,List<String>> res = new HashMap<>();
for(String s : strs){
char[] schar = s.toCharArray();
Arrays.sort(schar);
String subs = String.valueOf(schar);
if(!res.containsKey(subs)){
res.put(subs,new ArrayList<String>());
}
res.get(subs).add(s);
}
//res.values() 返回collection集合。
return new ArrayList<>(res.values());
}
}
方法2:建立数组映射
- 代码演示
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String,List<String>> res = new HashMap<>();
for(String str : strs){
int[] count = new int[26];
//记录当前str的每一个字符出现的次数
for(int i = 0 ; i < str.length();i++){
count[str.charAt(i) - 'a'] ++;
}
StringBuilder sb = new StringBuilder();
/*
将当前str中字符与其出现的次数进行拼接,作为key
若当前字符与出现次数拼接后,存在key,就说明当前字符有字母异位词,压入当前List。
否则,新建List压入。
*/
for(int i = 0 ; i < 26;i++){
if(count[i] != 0){
sb.append('a' + i);
sb.append(count[i]);
}
}
String s = sb.toString();
List<String> list = res.getOrDefault(s,new ArrayList<>());
list.add(str);
res.put(s,list);
}
return new ArrayList<>(res.values());
}
}
50.Pow(x, n)
- 题解:https://leetcode-cn.com/problems/powx-n/solution/50-powx-n-kuai-su-mi-qing-xi-tu-jie-by-jyd/
方法1:快速幂
- 思路:
- 代码演示:
class Solution {
public double myPow(double x, int n) {
if(n == 0 || x == 1){
return 1;
}
if(n == 0){
return 0;
}
double res = 1;
long b = n;
/*
Java 代码中 int32 变量 n∈[−2147483648,2147483647] ,因此当 n=−2147483648 时执行 n = -n 会因越界而赋值出错。
解决方法是先将 n 存入 long 变量 b ,后面用 b 操作即可。
*/
if(b < 0){
b = -b;
x = 1/x;
}
while(b > 0){
//b是奇数,
if((b & 1) == 1){
res = res * x;
}
x *= x;
b >>= 1;
}
return res;
}
}
方法2:分治 + 记忆化
class Solution {
int res = 0;
Map<Integer,Double> map = new HashMap<>();
public double myPow(double x, int n) {
if(n == 0 || x == 1){
return 1;
}
if(n == 0){
return 0;
}
if(n < 0){
n = -n;
x = 1/x;
}
return pow(x,n);
}
public double pow(double x,int n){
//必须为n==0 return 1 不能为n==1 rutrun x 。具体原因经过debug调试未知...
if( n == 0){
return 1;
}
double half = 0;
//看当前n/2是否运算过
if(map.containsKey(n/2)){
half = map.get(n/2);
}else{
half = pow(x,n/2);
map.put(n/2,half);
}
if(n % 2 == 0){
return half * half;
}else{
return half * half * x;
}
}
}
方法3:快速幂递归写法
class Solution {
int flag = 1;
public double myPow(double x, int n) {
//简化计算,flag判断的当前x正负,输出时做处理
if(n < 0){
flag = -flag;
n = Math.abs(n);
}
if(n == 0){
return 1;
}
if(n == 1){
return flag == 1 ? x : 1 / x;
}
double result = quickPow(x,n);
return flag == 1 ? result : 1 / result;
}
//快速幂:x5 = (x2)2 * x;
public double quickPow(double x ,int n){
if(n == 0){
return 1;
}
if(n == 1){
return x;
}
double res = 0;
if(n % 2 == 0){
res = quickPow(Math.pow(x,2),n / 2);
}else{
res = quickPow(Math.pow(x,2), n /2) * x;
}
return res;
}
}