前言
人还是太容易懒散,工作稍微一饱和,小说短视频一看,几个好友一浪,什么学习计划和目标都抛诸脑后了。今天总算是水到400题了,趁着热度还在,赶紧总结一下几个解题技巧吧。
1 哈希
Hash算法在一些题中,可以将暴力法时间复杂度为平方级O(n^2)降到线性级O(n),因为Hash算法的基本操作put,get,contains等都是O(1)的。Java中可以使用HashMap或者HashSet来达到目的。
经典的简单题两数之和的解法,只用最多一次循环就得到答案
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer,Integer> map=new HashMap<>(nums.length*2);
for(int i=0;i<nums.length;i++){
if(map.containsKey(target-nums[i])){
return new int[]{map.get(target-nums[i]),i};
}
map.put(nums[i],i);
}
return new int[]{};
}
}
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
示例:
输入: ["eat", "tea", "tan", "ate", "nat", "bat"]
输出:
[
["ate","eat","tea"],
["nat","tan"],
["bat"]
]
说明:
所有输入均为小写字母。
不考虑答案输出的顺序。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/group-anagrams
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
这个题用HashSet来判断重复
class Solution {
public List<String> findRepeatedDnaSequences(String s) {
Set<String> set=new HashSet<>();
Set<String> res=new HashSet<>();
int left=0;
int right=10;
while(right<=s.length()){
String sub=s.substring(left,right);
if(set.contains(sub)){
res.add(sub);
}
set.add(sub);
left++;
right++;
}
return new ArrayList<>(res);
}
}
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
示例 1:
输入: s = "anagram", t = "nagaram"
输出: true
示例 2:
输入: s = "rat", t = "car"
输出: false
说明:
你可以假设字符串只包含小写字母。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/valid-anagram
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
这个题中,因为只有小写字母,所有直接用一个26长度的数组来做映射,其实也是一个简版Hash算法
class Solution {
public boolean isAnagram(String s, String t) {
if(s.length()!=t.length()){
return false;
}
int[] map=new int[26];
for(int i=0;i<s.length();i++){
map[s.charAt(i)-'a']++;
}
boolean flag=true;
for(int i=0;i<t.length();i++){
int v = map[t.charAt(i)-'a']++;
v--;
if(v<0){
return false;
}
map[t.charAt(i)-'a']=v;
}
return flag;
}
}
2双指针
双指针是使用两个指针同时来处理,有时候一个指针从头开始,一个指针从尾部开始,从中间靠拢,有时候一个快一个慢,又叫快慢指针,二分搜索,滑动窗口也需要用到双指针,只是侧重点不同。
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
说明:本题中,我们将空字符串定义为有效的回文串。
示例 1:
输入: "A man, a plan, a canal: Panama"
输出: true
示例 2:
输入: "race a car"
输出: false
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/valid-palindrome
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
这个解法是一个指针从头开始,一个指针从尾部开始,不断靠拢,如果能熬到循环结束,说明是回文
class Solution {
public boolean isPalindrome(String s) {
if(s.length()==0||s.length()==1){
return true;
}
int l=0;
int r=s.length()-1;
while(l<r){
if(!(isChar(s.charAt(l))||isNumber(s.charAt(l)))){
l++;
continue;
}
if(!(isChar(s.charAt(r))||isNumber(s.charAt(r)))){
r--;
continue;
}
int cha = Math.abs(s.charAt(l)-s.charAt(r));
if(cha==0 || (cha==('a'-'A') && isChar(s.charAt(l))&&isChar(s.charAt(r)) )){
l++;r--;
continue;
}
return false;
}
return true;
}
public boolean isChar(char c){
return (c>='a'&&c<='z') ||(c>='A'&&c<='Z');
}
public boolean isNumber(char c){
return c>='0'&&c<='9';
}
}
实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。
注意:本题相对原题稍作改动
示例:
输入: 1->2->3->4->5 和 k = 2
输出: 4
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/kth-node-from-end-of-list-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
这里fast先移动k个位置,然后slow指针从头开始移动,等fast到最后了,slow就在倒数k的位置了,比起要反转链表是不是减少了额外的操作
class Solution {
public int kthToLast(ListNode head, int k) {
int fast,slow=0;
ListNode pre=head;
while(k>0){
k--;
head=head.next;
}
while(null!=head){
pre=pre.next;
head=head.next;
}
return pre.val;
}
}
3二分搜索
二分搜索,使用双指针的技巧,比对中间值的大小,然后缩小边界,可以将O(n)的时间复杂度降低到O(logn),极大的加快检索的速度,二叉搜索数也是这个道理。
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例 1:
输入: [1,3,5,6], 5
输出: 2
示例 2:
输入: [1,3,5,6], 2
输出: 1
示例 3:
输入: [1,3,5,6], 7
输出: 4
示例 4:
输入: [1,3,5,6], 0
输出: 0
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/search-insert-position
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
二分搜索查询插入位置
class Solution {
public int searchInsert(int[] nums, int target) {
int l=0,r=nums.length-1,res=nums.length;
while(l<=r){
int m=(l+r)>>1;
if(nums[m]==target){
return m;
}else if(nums[m]>target){
res=m;
r=m-1;
}else if(nums[m]<target){
l=m+1;
}
}
return res;
}
}
4前缀和
前缀和是对数组的每个index预处理与之前的所有元素的和,一般可以用来处理连续字串问题。
给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。
示例 1 :
输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。
说明 :
数组的长度为 [1, 20,000]。
数组中元素的范围是 [-1000, 1000] ,且整数 k 的范围是 [-1e7, 1e7]。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/subarray-sum-equals-k
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
这个题解法,用HashMap保持每个位置的前缀和,
class Solution {
public int subarraySum(int[] nums, int k) {
Map<Integer,Integer> map = new HashMap<>();
int res=0;
int count=0;
map.put(0,1);
for(int i=0;i<nums.length;i++){
count+=nums[i];
// System.out.println(map);
if(map.containsKey(count-k)){
res+=map.get(count-k);
}
map.put(count,map.getOrDefault(count,0)+1);
}
return res;
}
}
5单调栈
单调栈是遍历时同时,维持一个单调递增或者单调递减的先进后出栈结果,当不再能够维持这个性质时候,不断出栈,直到又可以维持单调性为止,依赖这个特性,一般可以来处理下一个更大值问题。
给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。
请你找出 nums1 中每个元素在 nums2 中的下一个比其大的值。
nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。
示例 1:
输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
输出: [-1,3,-1]
解释:
对于 num1 中的数字 4 ,你无法在第二个数组中找到下一个更大的数字,因此输出 -1 。
对于 num1 中的数字 1 ,第二个数组中数字1右边的下一个较大数字是 3 。
对于 num1 中的数字 2 ,第二个数组中没有下一个更大的数字,因此输出 -1 。
示例 2:
输入: nums1 = [2,4], nums2 = [1,2,3,4].
输出: [3,-1]
解释:
对于 num1 中的数字 2 ,第二个数组中的下一个较大数字是 3 。
对于 num1 中的数字 4 ,第二个数组中没有下一个更大的数字,因此输出 -1 。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/next-greater-element-i
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
Map<Integer,Integer> map = new HashMap<>();
Deque<Integer> stack=new LinkedList<>();
int cur=nums2[0];
for(int i=0;i<nums2.length;i++){
while(!stack.isEmpty()&&nums2[i]>stack.peek()){
map.put(stack.pop(),nums2[i]);
}
stack.push(nums2[i]);
}
int[] res=new int[nums1.length];
for(int i=0;i<nums1.length;i++){
res[i]=null==map.get(nums1[i])?-1:map.get(nums1[i]);
}
return res;
}
}
6单调队列
单调队列则类似,维持一个单调递增或者单调递减的先进后出栈结果,当不再能够维持这个性质时候,不断出队,直到又可以维持单调性为止,依赖这个特性,可以处理一定范围内的最值问题。
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
示例 1:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
示例 2:
输入:nums = [1], k = 1
输出:[1]
示例 3:
输入:nums = [1,-1], k = 1
输出:[1,-1]
示例 4:
输入:nums = [9,11], k = 2
输出:[11]
示例 5:
输入:nums = [4,-2], k = 2
输出:[4]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sliding-window-maximum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
窗口之内的最大值,可以用单调队列实现
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
Deque<Integer> queue=new LinkedList<>();
int[] res=new int[nums.length-k+1];
int left=0,right=0,resIndex=0;
while(right<nums.length){
int cur=nums[right];
right++;
while(!queue.isEmpty()&&queue.peekLast()<cur){
queue.pollLast();
}
queue.offer(cur);
if(right-left==k){
// System.out.println(queue);
res[left]=queue.peek();
int leftCur=nums[left];
if(queue.peek()==leftCur){
queue.poll();
}
left++;
}
}
return res;
}
}
7滑动窗口
滑动窗口算法,使用两个指针一前一后,当窗口未达到条件是,需要扩大窗口,当窗口达到条件,这时候需要一边缩小窗口,一边处理结果,但又不能达到条件时,需要跳出来接着扩大窗口。一般可以用来处理一些
给你一个字符串 s 和一个整数 k ,请你找出 s 中的最长子串, 要求该子串中的每一字符出现次数都不少于 k 。返回这一子串的长度。
示例 1:
输入:s = "aaabb", k = 3
输出:3
解释:最长子串为 "aaa" ,其中 'a' 重复了 3 次。
示例 2:
输入:s = "ababbc", k = 2
输出:5
解释:最长子串为 "ababb" ,其中 'a' 重复了 2 次, 'b' 重复了 3 次。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-substring-with-at-least-k-repeating-characters
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
class Solution {
public int longestSubstring(String s, int k) {
int maxlength=0;
for(int i=1;i<=26;i++){
int[] windowMap=new int[26];
int total=0,less=0;
int left=0,right=0;
if(i>s.length()){
continue;
}
while(right<s.length()){
char cur=s.charAt(right);
right++;
windowMap[cur-'a']++;
if(windowMap[cur-'a']==1){
total++;
less++;
}
if(windowMap[cur-'a']==k){
less--;
}
while(total>i){
int leftCur=s.charAt(left);
windowMap[leftCur-'a']--;
if(windowMap[leftCur-'a']==k-1){
less++;
}
if(windowMap[leftCur-'a']==0){
less--;
total--;
}
left++;
}
if(less==0){
maxlength=Math.max(maxlength,right-left);
}
}
}
return maxlength;
}
}
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
示例 4:
输入: s = ""
输出: 0
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character,Integer> map = new HashMap<>(1024);
int left=0,right=0;
int maxLength=0;
while(right<s.length()){
char cur=s.charAt(right);
right++;
if(map.containsKey(cur)){
left=Math.max(left,map.get(cur));
}
maxLength=Math.max(maxLength,right-left);
map.put(cur,right);
}
return maxLength;
}
}
结语
这些初级的小技巧,就可以大大降低问题时间复杂度,不禁让人惊喜,更何况那些高级的算法,为社会的发展做出了巨大贡献,这就是算法吸引人之处吧。此文涉及的题解时间跨度较大,描述不是太详细,因为有些我也记不起来了,而且写一篇文章相当耗费精力。