赞
踩
1.剑指 Offer II 009. 乘积小于 K 的子数组 (难度中等)
给定一个正整数数组 nums和整数 k ,请找出该数组内乘积小于 k 的连续的子数组的个数。
示例 1:
输入: nums = [10,5,2,6], k = 100 输出: 8 解释: 8 个乘积小于 100 的子数组分别为: [10],
[5], [2], [6], [10,5], [5,2], [2,6], [5,2,6]。 需要注意的是 [10,5,2]
并不是乘积小于100的子数组。 示例 2:输入: nums = [1,2,3], k = 0 输出: 0
来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/ZVAVXX
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
题解:一般题目中都会有明确的“连续子数组”、“连续子串”等关键字,另外可能会附带最大、最小的限定词进行补充,但是本题目没有最大、最小子数组,转而让求连续的子数组的个数,这种也是可以用滑动窗口解决的:
只需计算出最大子数组的子数组个数即可(这时最大子数组满足那么子数组的子串一定还满足)具体到此题:上面这道题只需找到以左边界开头符合条件的最大子数组,然后计算最大子数组的子数组个数,最后依次相加即可,代码如下:
package leetcode.offer; public class t9 { public static void main(String[] args) { t9 a = new t9(); System.out.println(a.numSubarrayProductLessThanK(new int[]{1, 2, 3}, 0)); } public int numSubarrayProductLessThanK(int[] nums, int k) { int left = 0, sum = 0, muti = 1, right = 0;//sum为满足条件的数组数 for (right = 0; right < nums.length; right++) {//右边界尽量右移 muti *= nums[right];//更新当前子数组乘积 while (muti >= k && left <= right) {//当加入right不满足条件时, if (muti / nums[right] < k) sum += right - left;//如果没加入时满足条件,说明以left开头的最长子数组为left——right-1,最长子数组的子数组(以left开头)个数为right-left muti /= nums[left++];//以left开头的最长子数组找到了,该寻找以left+1开头的了 } } //最后因为right到头而退出循环,但是left还没到头,所以需要接着处理 while (muti >= k && left < nums.length) {//如果right到头后muti还是大于k的,那以left开头的最长子数组一定是不包括right而包括right-1的 sum += right - 1 - left; left++; } if (left < nums.length) sum += (right - left) * (right - left + 1) / 2;//如果从某一项开始muti不大于k了,那直接找left——right的所有子数组(不必以left开头)即可 return sum; } }
优化:这种做法是计算以left开头的最大子数组的子数组个数的,其实可以计算以right结尾,这样right到头后不用接着处理了,优化代码剑t9adv
package leetcode.offer; public class t9 { public static void main(String[] args) { t9 a = new t9(); System.out.println(a.numSubarrayProductLessThanK(new int[]{1, 2, 3}, 0)); } //这种做法是计算以left开头的最大子数组的子数组个数的,其实可以计算以right结尾,这样right到头后不用接着处理了,优化代码剑t9adv public int numSubarrayProductLessThanK(int[] nums, int k) { int left = 0, sum = 0, muti = 1, right = 0;//sum为满足条件的数组数 for (right = 0; right < nums.length; right++) {//右边界尽量右移 muti *= nums[right];//更新当前子数组乘积 while (muti >= k && left <= right) {//当加入right不满足条件时, if (muti / nums[right] < k) sum += right - left;//如果没加入时满足条件,说明以left开头的最长子数组为left——right-1,最长子数组的子数组(以left开头)个数为right-left muti /= nums[left++];//以left开头的最长子数组找到了,该寻找以left+1开头的了 } } //最后因为right到头而退出循环,但是left还没到头,所以需要接着处理 while (muti >= k && left < nums.length) {//如果right到头后muti还是大于k的,那以left开头的最长子数组一定是不包括right而包括right-1的 sum += right - 1 - left; left++; } if (left < nums.length) sum += (right - left) * (right - left + 1) / 2;//如果从某一项开始muti不大于k了,那直接找left——right的所有子数组(不必以left开头)即可 return sum; } }
2.剑指OfferII010.和为k的子数组 (难度中等)
题目:剑指OfferII010.和为k的子数组 给定一个整数数组和一个整数 k ,请找到该数组中和为 k 的连续子数组的个数。
提示:
1 <= nums.length <= 2 * 10 ^ 4
-1000 <= nums[i] <= 1000
-10 ^ 7 <= k <= 10 ^ 7 示例示例 1 : 输入:nums = [1,1,1], k = 2 输出: 2 解释: 此题 [1,1] 与 [1,1] 为两种不同的情况
示例 2 : 输入:nums = [1,2,3], k = 3 输出: 2
示例3:输入:nums = [1,1,1,1,-1000,1000], k = 3 输出: 2
题解:本题思想很容易想到两个for循环暴力解法,示例3如下图所示
很容易看到i=1的一轮内层for循环所做的遍历相加在i=0那一轮其实已经做过一次了,只不过上一轮多计算了一个nums[0],减去nums[0]后的遍历和下一轮一模一样并不完全一样,i=1轮遍历数据个数为5,而i=0轮为6,所以应该将i=0轮的nums[i]出现的次数去掉1,这样保证了i=1轮不会搜索到上一轮第一个数据。所以想到可以把i=0的那一轮数据保存起来;在i=1轮时,保存的数据集体减去一个nums[0]同时nums[0]出现次数-1就可以用了;在i=2轮时,保存的数据集体减去一个nums[0]+nums[1]同时nums[0]+nums[1]出现次数-1就可以用了,以此类推。保存下i=0轮的数据需要On时间复杂度。
数据保存完毕后(假设使用数组保存),就可以开始查询了,i=0时查询k在数组中存在次数;i=1时,如果还查询k,那么数组元素应集体把nums[0]减去后再查,与其操作一边数组,不如直接查询k+nums[0]在数组存在次数;以此类推,i=2查询k+nums[0]+nums[1]在数组出现次数。
而查询一个元素在数组中出现次数需要On时间复杂度,所以用数组存储并不是一个好的选择,所以可以使用哈希表存储,这样只需O1时间复杂度。
代码如下:
package leetcode.offer; import com.sun.jdi.Value; import java.security.Key; import java.util.HashMap; public class T10adv { public static void main(String[] args) { System.out.println(new T10adv().subarraySum(new int[]{1,2,3},3)); } public int subarraySum(int[] nums, int k) { HashMap<Integer, Integer> hashMap = new HashMap<Integer, Integer>(); int pre_sum=0,total=0; for (int i = 0; i < nums.length; i++) {//保存一轮以0开头,不同结尾的前缀和 pre_sum+=nums[i]; if(hashMap.get(pre_sum)==null)hashMap.put(pre_sum,1); else hashMap.computeIfPresent(pre_sum,(Key, value)->value+1); } total+=hashMap.get(k)==null?0:hashMap.get(k);//查询k出现次数 pre_sum=k; for (int i = 0; i < nums.length-1; i++) { pre_sum+=nums[i];//第i+1轮 hashMap.replace(pre_sum-k,hashMap.get(pre_sum-k)-1);//去掉上一轮的第一个数据出现次数 total+=hashMap.get(pre_sum)==null?0:hashMap.get(pre_sum); } return total; } }
上述解题思路中可以看出,我们需要预先将i=0轮的前缀和全部保存下来,之后模拟i=1轮时又要去掉又要减去的,非常麻烦,于是想到:能否在计算i=0轮的某一次前缀和时就同时把后面要查找的k,k+nums[0]…k+k+nums[0]+…+一次查询完毕呢?示意图如下:
在计算pre_sum[i]时,同时查询是否有k,k+nums[0]…k+k+nums[0]+…+存在。换种说法,查询是否存在j使得pre_sum[i]-pre_sum[j]==k,即pre_sum[j]=pre_sum[i]-k,有多少个j,就代表本次有多少以j结尾的子数组满足等于k,代码如下:
package leetcode.offer; import java.util.HashMap; public class T10Best { public static void main(String[] args) { System.out.println(new T10Best().subarraySum(new int[]{1,2,3},3)); } public int subarraySum(int[] nums, int k) { int pre_sum=0,total=0; HashMap<Integer,Integer> map=new HashMap<Integer,Integer>(); map.put(0,1);//pre_sum[j]的j前缀和不包含一个元素时 for (int i = 0; i < nums.length; i++) { pre_sum+=nums[i]; total+=map.getOrDefault(pre_sum-k,0);//get之后放put,因为这个pre_sum[j]不能是j==i的情况,所以本轮pre_sum[i]get完再放。 map.put(pre_sum,map.getOrDefault(pre_sum,0)+1); } return total; } }
3. 剑指 Offer II 011. 0 和 1 个数相同的子数组难度中等
给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。
示例 1:
输入: nums = [0,1] 输出: 2 说明: [0, 1] 是具有相同数量 0 和 1 的最长连续子数组。 示例 2:
输入: nums = [0,1,0] 输出: 2 说明: [0, 1] (或 [1, 0]) 是具有相同数量 0 和 1 的最长连续子数组。
本题没让求和,让找1,0出现相同次数的子串,可以类比前缀和,0视为-1,1还是1,照样求子数组和,和为0的子数组满足条件。
package leetcode.offer; import java.util.HashMap; /** * 给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。 * 右边界扩张后不满足条件时,继续扩张仍有可能满足条件,不能用滑动窗口 */ public class T11 { public static void main(String[] args) { System.out.println(new T11().findMaxLength(new int[]{0,1})); } public int findMaxLength(int[] nums) { int pre_sum=0; int max=0;//记录最长子数组长度 HashMap<Integer,Integer> map=new HashMap<>(); map.put(0,0); for (int i = 0; i < nums.length; i++) { pre_sum+=(nums[i]==0?-1:1); if(map.get(pre_sum)!=null) max=Math.max(max,i+1-map.get(pre_sum)); map.putIfAbsent(pre_sum,i+1); } return max; } }
4.剑指 Offer II 012. 左右两边子数组的和相等难度简单
给你一个整数数组 nums ,请计算数组的 中心下标 。
数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。
如果中心下标位于数组最左端,那么左侧数之和视为 0 ,因为在下标的左侧不存在元素。这一点对于中心下标位于数组最右端同样适用。
如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回 -1 。
示例 1:
输入:nums = [1,7,3,6,5,6] 输出:3 解释: 中心下标是 3 。 左侧数之和 sum = nums[0] +
nums[1] + nums[2] = 1 + 7 + 3 = 11 , 右侧数之和 sum = nums[4] + nums[5] = 5
- 6 = 11 ,二者相等。
题解:这题很简单,直接先初始化i=0时的左右子数组之和,然后i++,同时更新左右子数组即可
class Solution {
public int pivotIndex(int[] nums) {
int left_sum=0,right_sum=0;//左右子数组和
for (int i = 1; i < nums.length; i++) {
right_sum+=nums[i];
}
if(right_sum==0) return 0;
for (int i = 1; i < nums.length; i++) {
left_sum+=nums[i-1];
right_sum-=nums[i];
if(right_sum==left_sum) return i;
}
return -1;
}
}
5.303. 区域和检索 - 数组不可变难度简单
给定一个整数数组 nums,求出数组从索引 i 到 j(i ≤ j)范围内元素的总和,包含 i、j 两点。
实现 NumArray 类:
NumArray(int[] nums) 使用数组 nums 初始化对象 int sumRange(int i, int j) 返回数组
nums 从索引 i 到 j(i ≤ j)范围内元素的总和,包含 i、j 两点(也就是 sum(nums[i], nums[i + 1],
… , nums[j]))示例:
输入: [“NumArray”, “sumRange”, “sumRange”, “sumRange”] [[[-2, 0, 3, -5,
2, -1]], [0, 2], [2, 5], [0, 5]] 输出: [null, 1, -1, -3]解释: NumArray numArray = new NumArray([-2, 0, 3, -5, 2, -1]);
numArray.sumRange(0, 2); // return 1 ((-2) + 0 + 3)
numArray.sumRange(2, 5); // return -1 (3 + (-5) + 2 + (-1))
numArray.sumRange(0, 5); // return -3 ((-2) + 0 + 3 + (-5) + 2 + (-1))
题解:这题很简单,让求子数组i-j的和,直接利用前缀和做差即可
class NumArray { private int[] nums; private int[] pre_sum; public NumArray(int[] nums) { this.nums= Arrays.copyOf(nums,nums.length); if(nums.length==0) { pre_sum=new int[]{}; return; } pre_sum =new int[nums.length]; pre_sum[0]=nums[0]; for (int i = 1; i < nums.length; i++) { pre_sum[i]=pre_sum[i-1]+nums[i]; } } public int[] getNums() { return nums; } public int[] getPre_sum() { return pre_sum; } public int sumRange(int left, int right) { if(left==0) return pre_sum[right]; return pre_sum[right]-pre_sum[left-1]; } } /** * Your NumArray object will be instantiated and called as such: * NumArray obj = new NumArray(nums); * int param_1 = obj.sumRange(left,right); */
6.剑指 Offer II 013. 二维子矩阵的和 难度中等
给定一个二维矩阵 matrix,以下类型的多个请求:
计算其子矩形范围内元素的总和,该子矩阵的左上角为 (row1, col1) ,右下角为 (row2, col2) 。 实现
NumMatrix 类:NumMatrix(int[][] matrix) 给定整数矩阵 matrix 进行初始化 int sumRegion(int row1,
int col1, int row2, int col2) 返回左上角 (row1, col1) 、右下角 (row2, col2)
的子矩阵的元素总和。示例 1:
输入: [“NumMatrix”,“sumRegion”,“sumRegion”,“sumRegion”]
[[[[3,0,1,4,2],[5,6,3,2,1],[1,2,0,1,5],[4,1,0,1,7],[1,0,3,0,5]]],[2,1,4,3],[1,1,2,2],[1,2,2,4]]
输出: [null, 8, 11, 12]解释: NumMatrix numMatrix = new
NumMatrix([[3,0,1,4,2],[5,6,3,2,1],[1,2,0,1,5],[4,1,0,1,7],[1,0,3,0,5]]]);
numMatrix.sumRegion(2, 1, 4, 3); // return 8 (红色矩形框的元素总和)
numMatrix.sumRegion(1, 1, 2, 2); // return 11 (绿色矩形框的元素总和)
numMatrix.sumRegion(1, 2, 2, 4); // return 12 (蓝色矩形框的元素总和)
这题为一维情况的推广,给定任意子矩阵求和,都可以转换为两个前缀子矩阵的差,由此定义一个前缀和Sum(0i,0j)
但是也可以把二维情况分解到一维逐维讨论,即求二维子矩阵的和转变为求每一行子数组的和,代码如下
class NumMatrix { private int[][]matrix; NumArray[] numArray; public NumMatrix(int[][] matrix) { this.matrix= matrix; //初始化每一行的前缀和数组 numArray=new NumArray[matrix.length]; for (int i = 0; i < matrix.length; i++) { numArray[i]=new NumArray(matrix[i]); } } public int sumRegion(int row1, int col1, int row2, int col2) { int sum=0; for (int i =row1; i <=row2; i++) { sum+=numArray[i].sumRange(col1,col2); } return sum; } public int[][] getMatrix() { return this.matrix; } } class NumArray { public static void main(String[] args) { } private int[] nums;//数组本身 private int[] pre_sum;//前缀和数组 public NumArray(int[] nums) { this.nums= Arrays.copyOf(nums,nums.length); if(nums.length==0) { pre_sum=new int[]{}; return; } //初始化前缀和数组 pre_sum =new int[nums.length]; pre_sum[0]=nums[0]; for (int i = 1; i < nums.length; i++) { pre_sum[i]=pre_sum[i-1]+nums[i]; } } public int sumRange(int left, int right) { //利用前缀和数组求解 if(left==0) return pre_sum[right]; return pre_sum[right]-pre_sum[left-1]; } } /** * Your NumMatrix object will be instantiated and called as such: * NumMatrix obj = new NumMatrix(matrix); * int param_1 = obj.sumRegion(row1,col1,row2,col2); */
给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的某个变位词。
换句话说,第一个字符串的排列之一是第二个字符串的 子串 。
示例 1:
输入: s1 = “ab” s2 = “eidbaooo”
输出: True
解释: s2 包含 s1 的排列之一 (“ba”).
示例 2:
输入: s1= “ab” s2 = “eidboaoo”
输出: False
用left,right遍历字符串s2,当right扩张后,新加入的right对应字母使子字符串比s1多了不该有的字母时,left收缩到一定程度可以使子字符串重新有可能符合条件。代码如下:
package leetcode.offer; import java.util.Arrays; import java.util.HashMap; public class T14 { public static void main(String[] args) { System.out.println(new T14().checkInclusion("adc", "dcda")); } public boolean checkInclusion(String s1, String s2) { int[] count = new int[26]; int len1 = s1.length(), len2 = s2.length(); for (int i = 0; i < len1; i++) {//计算s1数组的状态(各字母出现次数) count[s1.charAt(i) - 'a']++; } int left = 0, right = 0; int[] now_count=new int[26]; for (right = 0; right < len2; right++) {//右边界尽量右移 now_count[s2.charAt(right) - 'a']+=1; if(Arrays.equals(now_count,count))return true; while(T14.compare(now_count,count)&&left<=right){//当部分已经不满足条件,左边界右移 now_count[s2.charAt(left++) - 'a']-=1; } } return false; } public static boolean compare(int[] a,int[] b){ for (int i = 0; i < a.length&&i<b.length; i++) { if(a[i]>b[i]) return true; } return false; } }
8.剑指 Offer II 015. 字符串中的所有变位词 难度中等
给定两个字符串 s 和 p,找到 s 中所有 p 的 变位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
变位词 指字母相同,但排列不同的字符串。
示例 1:
输入: s = “cbaebabacd”, p = “abc” 输出: [0,6] 解释: 起始索引等于 0 的子串是 “cba”, 它是
“abc” 的变位词。 起始索引等于 6 的子串是 “bac”, 它是 “abc” 的变位词。 示例 2:输入: s = “abab”, p = “ab” 输出: [0,1,2] 解释: 起始索引等于 0 的子串是 “ab”, 它是 “ab”
的变位词。 起始索引等于 1 的子串是 “ba”, 它是 “ab” 的变位词。 起始索引等于 2 的子串是 “ab”, 它是 “ab”
的变位词。
本题与上一题基本一样,只不过上一题找到一个子串就返回true了,而本题需要记录所有满足的子串,把返回true改为记录left即可,代码如下:
package leetcode.offer; import java.util.*; public class T15 { public static void main(String[] args) { System.out.println(new T15().findAnagrams("cbaebabacd","abc")); } public List<Integer> findAnagrams(String s, String p) { List<Integer> res = new ArrayList<Integer>(); int left = 0, len1 = s.length(), len2 = p.length(); int[] count = new int[26], now_count = new int[26]; for (int i = 0; i < len2; i++) { count[p.charAt(i)-'a']++; } for (int right = 0; right <len1; right++) {//右边界尽量扩张 now_count[s.charAt(right)-'a']++;//扩张后更新当前子串状态 if(Arrays.equals(now_count,count))res.add(left); while(T15.compare(now_count,count)&&left<=right){ now_count[s.charAt(left++)-'a']--; if(Arrays.equals(now_count,count))res.add(left); } } return res; } public static boolean compare(int[] a,int[] b){ for (int i = 0; i < a.length; i++) { if(a[i]>b[i]) return true; } return false; } }
9.剑指 Offer II 016. 不含重复字符的最长子字符串 难度中等
给定一个字符串 s ,请你找出其中不含有重复字符的 最长连续子字符串 的长度。
示例 1:
输入: s = “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子字符串是 “abc”,所以其长度为 3。 示例 2:
输入: s = “bbbbb” 输出: 1 解释: 因为无重复字符的最长子字符串是 “b”,所以其长度为 1。 示例 3:
输入: s = “pwwkew” 输出: 3 解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。 示例 4:输入: s = “” 输出: 0
提示:
0 <= s.length <= 5 * 104 s 由英文字母、数字、符号和空格组成
本题是典型的滑动窗口题,正常做就行,唯一注意的是字符串可能出现符号和数字字母,数量超过32位,必须用一个数组存储出现次数,代码如下:
class Solution { public int lengthOfLongestSubstring(String s) { if(s.equals(""))return 0; int left=0,right=0,len=s.length(); // long bitmask=0;//位掩码存不下字符串可能出现的字符、字母、数字 // int max=1;//记录子串出现与否 // for (right = 0; right < len; right++) {//右边界尽量右移 // while((bitmask&(1<<(s.charAt(right)-'a')))!=0){//当右移后产生了重复字母,则左边界就不得不收缩 // left++; // if(left==right) break; // bitmask^=1<<s.charAt(left-1)-'a'; // } // bitmask|=1<<s.charAt(right)-'a';//去掉右移前的重复字母之后再将右移的字母添加上去 // max=Math.max(max,right-left+1); // } // return max; int[] mask=new int[100]; int max=1; for (right = 0; right < len; right++) {//右边界尽量右移 while(mask[s.charAt(right)-' ']!=0){//当右移后产生了重复字母,则左边界就不得不收缩 left++; if(left==right) break; mask[s.charAt(left-1)-' ']=0; } mask[s.charAt(right)-' ']=1;//去掉右移前的重复字母之后再将右移的字母添加上去 max=Math.max(max,right-left+1); } return max; } }
10.剑指 Offer II 017. 含有所有字符的最短字符串 难度中等
给定两个字符串 s 和 t 。返回 s 中包含 t 的所有字符的最短子字符串。如果 s 中不存在符合条件的子字符串,则返回空字符串 “” 。
如果 s 中存在多个符合条件的子字符串,返回任意一个。
注意: 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
示例 1:
输入:s = “ADOBECODEBANC”, t = “ABC” 输出:“BANC” 解释:最短子字符串 “BANC” 包含了字符串 t
的所有字符 ‘A’、‘B’、‘C’ 示例 2:输入:s = “a”, t = “a” 输出:“a” 示例 3:
输入:s = “a”, t = “aa” 输出:"" 解释:t 中两个字符 ‘a’ 均应包含在 s
的子串中,因此没有符合条件的子字符串,返回空字符串。提示:
1 <= s.length, t.length <= 105 s 和 t 由英文字母组成
滑动窗口题型,左边界尽量收缩,收缩时若子串不满足条件,则右边界扩张,直至重新满足条件,左边界继续收缩,以此类推,代码如下:
class Solution { public String minWindow(String s, String t) { int[] now_state=new int[64],count=new int[64]; int right=0,len1=t.length(),len2=s.length(); int min=len2+1; String res=null; for (int i = 0; i < len1; i++) {//初始化t状态数组 count[t.charAt(i)-'A']++; } for (int left = 0; left <len2 ; left++) {//左边界尽量收缩 if(left!=0) now_state[s.charAt(left-1)-'A']--; while(right<len2&¬contain(now_state,count)){ now_state[s.charAt(right++)-'A']++; } if(min>right-left&&!notcontain(now_state,count)){ min=right-left; res=s.substring(left,right); } } return res==null?"":res; } public boolean notcontain(int[] a,int[] b){ for (int i = 0; i < a.length; i++) { if(a[i]<b[i]) return true; } return false; } }
11.剑指 Offer II 019. 最多删除一个字符得到回文 难度简单
给定一个非空字符串 s,请判断如果 最多 从字符串中删除一个字符能否得到一个回文字符串。
示例 1:输入: s = “aba” 输出: true
示例 2:输入: s = “abca” 输出: true 解释: 可以删除 “c” 字符 或者 “b” 字符 示例 3:
输入: s = “abc” 输出: false
提示:
1 <= s.length <= 10^5
s 由小写英文字母组成
题解:这题很简单,只不过在前面某题求回文串的基础上加上不满足则头或尾指针跳一格重新检查,代码如下:
package leetcode.offer; public class T19 { public static void main(String[] args) { System.out.println(new T19().validPalindrome("aguokepatgbnvfqmgmlcupuufxoohdfpgjdmysgvhmvffcnqxjjxqncffvmhvgsymdjgpfdhooxfuupuculmgmqfvnbgtapekouga")); } public boolean validPalindrome(String s) { int front=0,rear=s.length()-1,flag=0; int temp1=0,temp2=0; while (front<rear){//设置头尾指针,向中间遍历检查是否是回文串 if(s.charAt(front)!=s.charAt(rear)){//头尾不相等,检查是否能头尾其中之一可以前进一格重新相等 if(flag==-1) return false;//已经删过一次了 if(flag==1){//这次换尾前进 front=temp1; rear=temp2-1; flag=-1; } else if(s.charAt(front+1)==s.charAt(rear)&&s.charAt(front)==s.charAt(rear-1)){//头尾都可以前进 temp1=front; temp2=rear; flag=1; front++;//这一次暂时头前进,如果后续又不满足,则换尾前进 } else if(s.charAt(front+1)==s.charAt(rear)) {//只有头可以,则头前进 front++; flag=-1; } else if(s.charAt(front)==s.charAt(rear-1)) {//只有尾可以,则尾前进 rear--; flag=-1; } else return false;//都不可以 } rear--; front++; } return true; } }
给定一个字符串 s ,请计算这个字符串中有多少个回文子字符串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
12.剑指 Offer II 020. 回文子字符串的个数 难度中等
示例 1: 输入:s = “abc” 输出:3 解释:三个回文子串: “a”, “b”, “c” 示例 2: 输入:s = “aaa”
输出:6 解释:6个回文子串: “a”, “a”, “a”, “aa”, “aa”, “aaa” 提示: 1 <= s.length <=
1000 s 由小写英文字母组成
题解:
采用两个for循环,对于每一个子字符串s.substring(i,j)是否为回文串进行判断,
判断其是否是回文串的命题都可以分解为判断
1、s.substring(i+1,j-1)是否为回文串和2、字符s[i]、s[j]是否相等
注意:对以后for循环的字串的判断还有可能用到前面小问题的历史记录,所以要全保存下来
代码: package leetcode.offer; import java.util.TooManyListenersException; import java.util.concurrent.ThreadPoolExecutor; public class T20adv { public static void main(String[] args) { System.out.println(new T20adv().countSubstrings("abc")); } private int[][] isPalindrome;//isPalindrome[j][i]记录从i到j的子串是否是回文串,是则存1,不是则存-1,未判断则存0 public int countSubstrings(String s) { int total=0; isPalindrome=new int[s.length()][]; for (int i = 0; i < s.length(); i++) { isPalindrome[i]=new int[i+1]; } for (int i = 0; i < s.length(); i++) { int j=0; while (j<=i){ total+=isPalindrome(s,j++,i)==true?1:0; } } return total; } public boolean isPalindrome(String s,int i,int j) { if(isPalindrome[j][i]==1)return true; if(s.charAt(i)!=s.charAt(j)) { isPalindrome[j][i] = -1; return false; } if(i==j||i==j-1) return true; if(isPalindrome[j-1][i+1]==-1){ isPalindrome[j][i]=-1; return false; } if(isPalindrome[j-1][i+1]==0){ if(isPalindrome(s,i+1,j-1)){ isPalindrome[j][i]=1; return true; } isPalindrome[j][i]=0; return false; } if(isPalindrome[j-1][i+1]==1){ isPalindrome[j][i]=1; return true; } return false; } }
13。 剑指 Offer II 021. 删除链表的倒数第 n 个结点 难度中等
给定一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5] 示例 2: 输入:head = [1], n = 1
输出:[]示例 3: 输入:head = [1,2], n = 1 输出:[1]
提示:
链表中结点的数目为 sz 1 <= sz <= 30 0 <= Node.val <= 100 1 <= n <= sz
题解:找倒数第n个节点,fast先走n步,之后slow、fast一块走,fast走到null结尾,slow刚好指向倒数第n个节点。
package leetcode.offer; class ListNode { int val; ListNode next; ListNode() {} ListNode(int val) { this.val = val; } ListNode(int val, ListNode next) { this.val = val; this.next = next; } } public class T21 { public static void main(String[] args) { } public ListNode removeNthFromEnd(ListNode head, int n) { ListNode slow=head,fast=head,temp=head;//fast比slow.next先走n步,当fast走到null时,slow.next就是倒数第n个节点 for (int i = 0; i < n; i++) { fast=fast.next;//fast先走n步 } if(fast==null) return head.next;//如果要删除头节点(倒数第head.length个),直接返回head.next fast=fast.next;//多走一步,让slow指向要删除节点的前一个节点 while(fast!=null){//一块走 slow=slow.next; fast=fast.next; } slow.next=slow.next.next;//删除slow.next return head; } }
14.剑指 Offer II 022. 链表中环的入口节点 难度中等
给定一个链表,返回链表开始入环的第一个节点。 从链表的头节点开始沿着 next 指针进入环的第一个节点为环的入口节点。如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是
-1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。说明:不允许修改给定的链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:返回索引为 1 的链表节点 解释:链表中有一个环,其尾部连接到第二个节点。 示例 2:
输入:head = [1,2], pos = 0 输出:返回索引为 0 的链表节点 解释:链表中有一个环,其尾部连接到第一个节点。 示例 3:
输入:head = [1], pos = -1 输出:返回 null 解释:链表中没有环。
提示:
链表中节点的数目范围在范围 [0, 104] 内
-105 <= Node.val <= 105 pos 的值为 -1 或者链表中的一个有效索引
题解:查找某个链表是否有环
让fast走快一点(两步),slow走慢一点(一步)。
如果有环,因为fast走得快,只要等slow进入了环,fast最后一定会追上slow,同时fast相对slow每次多走一步,那就不会出现fast追过头了slow的,一定追上且相遇,即当fast==slow就代表有环。
如果没环,fast肯定会走到头,走到null的,当fast == null || fast.next== null时证明无环。
进阶:
查询环的入口节点?解:fast最后能刚好追上slow代表fast一定是比slow多走了几个环的长度(不一定是一个环),记录下相遇时fast、slow总共走的步数fcount、scount;将fast、slow重置为head,令fast提前将几个环的长度fcount-scount走完,之后fast和slow同步走,最后相遇一定是在环入口处相遇。
package leetcode.offer; import javax.swing.*; public class T22 { public static void main(String[] args) { } public ListNode detectCycle(ListNode head) { ListNode slow = head, fast = head; int s_count = 0, f_count = 0; while (fast != null && fast.next != null) {//只要fast没走到头 fast = fast.next.next;//fast走两步,slow走一步 f_count += 2;//记录fast步数 slow = slow.next; s_count += 1; if (fast == slow) {//相遇后,代表有环,开始求环入口 fast = head;//重置fast,slow slow = head; for (int i = 0; i < f_count - s_count; i++) { fast=fast.next;//fast先将几个环长度f_count - s_count走完 } s_count=0;//记录入口索引值 while(fast!=slow){//同时走,相遇处即为入口 slow=slow.next; fast=fast.next; } return fast; } } return null;//走到头代表没环 } }
15.剑指 Offer II 023. 两个链表的第一个重合节点 难度简单
给定两个单链表的头节点 headA 和 headB ,请找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
图示两个链表在节点 c1 开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA
= 2, skipB = 3 输出:Intersected at ‘8’ 解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。 从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。 在 A 中,相交节点前有 2
个节点;在 B 中,相交节点前有 3 个节点。 示例 2:输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3,
skipB = 1 输出:Intersected at ‘2’ 解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。 在 A 中,相交节点前有 3 个节点;在 B
中,相交节点前有 1 个节点。 示例 3:输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB
= 2 输出:null 解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。 由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。 这两个链表不相交,因此返回 null 。提示:
listA 中节点数目为 m listB 中节点数目为 n 0 <= m, n <= 3 * 104 1 <= Node.val <=
105 0 <= skipA <= m 0 <= skipB <= n 如果 listA 和 listB 没有交点,intersectVal
为 0 如果 listA 和 listB 有交点,intersectVal == listA[skipA + 1] ==
listB[skipB + 1]进阶:能否设计一个时间复杂度 O(n) 、仅用 O(1) 内存的解决方案?
题解:让长链表和短链表的遍历指针指向同一位置,然后同步向后遍历,直到为空或相等。
package leetcode.offer; public class T23 { public static void main(String[] args) { } public ListNode getIntersectionNode(ListNode headA, ListNode headB) { int len_subtract=0; ListNode fast=headA,slow=headB; while(fast!=null&&slow!=null){//移动到其中一方为null,剩下一方到null的距离为两链表长度之差 fast=fast.next; slow=slow.next; } while(fast!=null){//计算长度之差 fast=fast.next; len_subtract++;//用正负标记是哪一个更长,+则headA长,-headB长 } while(slow!=null){ slow=slow.next; len_subtract--; } if(len_subtract<0){//谁长让fast指向谁,并让fast提前走完两链表长度之差,这样两指针指向同一个位置 fast=headB; slow=headA; len_subtract=-len_subtract; } else{ fast=headA; slow=headB; } for (int i = 0; i < len_subtract; i++) { fast=fast.next; } while(fast!=slow&&fast!=null){//判断交点位置,为null也没关系,直接返回 fast=fast.next; slow=slow.next; } return fast; } }
16 53. 最大子序和
难度简单
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1]
输出:1
示例 3:
输入:nums = [0]
输出:0
示例 4:
输入:nums = [-1]
输出:-1
示例 5:
输入:nums = [-100000]
输出:-100000
提示:
1 <= nums.length <= 3 * 104
-105 <= nums[i] <= 105
如果采用动态规划的方法,那么需要考虑当前问题能否通过小问题的答案简单推导出来,假设Pn-1的连续子数组最大之和已经得到,在末尾加入4之后能否推到出Pn的连续子数组最大之和呢?由于Pn-1的最大连续子数组位置并不确定,如果以-5结尾还好,加上4就行,但是明显最大子数组可以出现在Pn-1的任意位置,**所以动态规划的命题不能为:大问题求最大连续子数组,小问题求最大连续子数组。那我们能否折中一下,就让4加入进去之后能够直接续上Pn-1呢?显然稍微修改下动态规划的命题即可:求以n结尾的最大连续子数组之和即可,**这样Pn以n结尾的最大子数组要么是Pn-1+nums[n],要么是nums[n]本身,解决之后再回过头看原命题,发现只用维护一个max为所有以i结尾的最大连续子数组之和中的最大值即可。
这道题给我们的最大启示是:用动态规划的备忘录元素不一定非要是题目要求的那个含义,有时候稍微降低下要求才能解的出来。
class Solution {
public int maxSubArray(int[] nums) {
int max=nums[0],pre_sum=nums[0];//max记录最大子数组值,pre_sum记录以i结尾的最大子数组之和
for (int i = 1; i < nums.length; i++) {
pre_sum=Math.max(pre_sum+nums[i],nums[i]);
max=Math.max(max,pre_sum);
}
return max;
}
}
17.剑指 Offer II 025. 链表中的两数相加 难度中等
给定两个 非空链表 l1和 l2
来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。可以假设除了数字 0 之外,这两个数字都不会以零开头。
示例1:
输入:l1 = [7,2,4,3], l2 = [5,6,4] 输出:[7,8,0,7] 示例2:
输入:l1 = [2,4,3], l2 = [5,6,4] 输出:[8,0,7] 示例3:
输入:l1 = [0], l2 = [0] 输出:[0]
提示:
链表的长度范围为 [1, 100] 0 <= node.val <= 9 输入数据保证链表代表的数字无前导 0
进阶:如果输入链表不能修改该如何处理?换句话说,不能对列表中的节点进行翻转。
题解:反转两链表,之后依次遍历,具体流程见代码注释
package leetcode.offer; public class T25 { public static void main(String[] args) { } public ListNode addTwoNumbers(ListNode l1, ListNode l2) { T24 t24=new T24(); l1=t24.reverseList(l1);//将待加的两链表翻转过来,这样方便从头结点个位数开始遍历 l2=t24.reverseList(l2); ListNode pointer=new ListNode();//设置两加位之和存储的节点 int carry=0;//进位 pointer.val=(l1.val+ l2.val)%10;//l1,l2长度都大于0,先对pointer初始化一次,这样一进入while循环就可以new新节点并插入链表 carry=(l1.val+ l2.val)>=10?1:0; if(l1!=null) l1=l1.next; if(l2!=null) l2=l2.next; while (l1!=null||l2!=null){//还有一个链表没遍历完,一定还会产生加数 pointer=new ListNode(-1,pointer);//new个结果节点,并用头插法插入链表 int t=(l1==null?0:l1.val)+ (l2==null?0:l2.val)+carry;//计算结果节点值 pointer.val= t%10; carry=t>=10?1:0;//更新进位 if(l1!=null) l1=l1.next;//更新两链表指针 if(l2!=null) l2=l2.next; } if(carry==1) pointer=new ListNode(1,pointer);//处理循环结束后的进位 return pointer; } }
18.剑指 Offer II 026. 重排链表 难度中等
给定一个单链表 L 的头节点 head ,单链表 L 表示为:
L0 → L1 → … → Ln-1 → Ln 请将其重新排列后变为:
L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例 1:
输入: head = [1,2,3,4] 输出: [1,4,2,3] 示例 2:
输入: head = [1,2,3,4,5] 输出: [1,5,2,4,3] 提示: 链表的长度范围为 [1, 5 * 104] 1 <=
node.val <= 1000
题解:任意举两个例子,可以看出最重要得到的链表都是把后半部分的链表节点插入到前半链表当中,也就是说此题要同时操作前后两部分链表,同时前半部分可以正向插入,但后半部分被插入时是逆序插入的。有了这几点就可以解题了:
找到两半部分链表起始点,其一i为head,其二j应为中间节点(快慢指针)的后一个节点
对后半部分逆序处理,由于允许修改原链表,原地反转后半部分
i,j遍历两部分即可
package leetcode.offer; public class T26 { public static void main(String[] args) { } public void reorderList(ListNode head) { ListNode fast=head,slow=head; while(fast!=null&&fast.next!=null){//找到链表的中间节点 fast=fast.next.next; slow=slow.next; } slow.next=reverseList(slow.next);//反转后半部分子链表 ListNode i=head,j=slow.next,temp;//设置被插入的前半链表遍历指针i,和所插入的后半链表遍历指针j,temp用于暂存j.next防止丢失 while(j!=null){//只要j不为null temp=j.next;//暂存 j.next=i.next;//插入j到i的后面 i.next=j; i=j.next;//i,j前进 j=temp; } slow.next=null;//做结尾的处理 } public static ListNode reverseList(ListNode head) { if(head==null||head.next==null)return head; ListNode l=head,n=head.next,r=head.next.next; head.next=null; while(r!=null){ n.next=l; l=n; n=r; r=r.next; } n.next=l; return n; } }
19.剑指 Offer II 028. 展平多级双向链表 难度中等
多级双向链表中,除了指向下一个节点和前一个节点指针之外,它还有一个子链表指针,可能指向单独的双向链表。这些子列表也可能会有一个或多个自己的子项,依此类推,生成多级数据结构,如下面的示例所示。
给定位于列表第一级的头节点,请扁平化列表,即将这样的多级双向链表展平成普通的双向链表,使所有结点出现在单级双链表中。
示例 1:
输入:head = [1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12]
输出:[1,2,3,7,8,11,12,9,10,4,5,6] 解释:输入的多级列表如下图所示:
扁平化后的链表如下图:
示例 2:
输入:head = [1,2,null,3] 输出:[1,3,2] 解释:
输入的多级列表如下图所示:
1—2---NULL
|
3—NULL
示例 3:
输入:head = [] 输出:[]
如何表示测试用例中的多级链表?
以 示例 1 为例:
1—2---3—4---5—6–NULL
| 7---8---9---10--NULL | 11--12--NULL
- 1
- 2
- 3
- 4
- 5
- 6
- 7
序列化其中的每一级之后:
[1,2,3,4,5,6,null] [7,8,9,10,null] [11,12,null]
为了将每一级都序列化到一起,我们需要每一级中添加值为 null 的元素,以表示没有节点连接到上一级的上级节点。[1,2,3,4,5,6,null] [null,null,7,8,9,10,null] [null,11,12,null]
合并所有序列化结果,并去除末尾的 null 。[1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12]
提示:
节点数目不超过 1000 1 <= Node.val <= 10^5
题解:本题不可能自底向上的用动态规划求解,只能递归的求解,假设函数flatten_rear(Node head)可以将head多级链表扁平化,那么具体过程应该如下:
判断当前链表是否用扁平化,即是否存在某一结点child步为空,如果不存在(递归出口)则直接返回当前链表尾节点。
存在child不为空,调用flatten_rear(Node child)将子多级链表扁平化。
将扁平化后的子链表插入当前链表。
找到尾节点并返回。
package leetcode.offer; import javax.print.attribute.standard.MediaSize; class Node { public int val; public Node prev; public Node next; public Node child; } public class T28 { public Node flatten(Node head) { if(head==null) return head;//空链表直接返回 Node pointer=head,temp; flatten_rear(head);//扁平化当前链表 return head; } public static Node flatten_rear(Node head){//将head指向的多级链表扁平化并返回扁平化的尾节点 Node pointer=head,child=null; while(pointer.next!=null){//判断当前链表是由有子链表,即是否有非空pointer.child if(pointer.child!=null) { child = pointer.child; break;//有则退出 } pointer=pointer.next; } if(pointer.child!=null) child = pointer.child;//最后的尾节点单独判断有无child if(child==null) return pointer;//如果没有则不用扁平化,直接返回尾节点pointer Node rear=flatten_rear(child);//有child,将child所指向的多级链表扁平化并暂存其尾节点 rear.next=pointer.next;//将扁平化后的链表作为整体插入到当前链表中 if(pointer.next!=null)pointer.next.prev=rear; pointer.next=child; pointer.child=null; child.prev=pointer; while(rear.next!=null)rear=rear.next;//返回当前扁平化后链表的尾节点 return rear; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。