算法-滑动窗口-题目与技巧

153 阅读2分钟

题目

leetcode-438 找到字符串中所有字母异位词(未通过)

题目

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。

代码

var findAnagrams = function(s, p) {
  const sLen = s.length, pLen = p.length;

  if (sLen < pLen) {
      return [];
  }

  const ans = [];

  // 26 字母表
  const sCount = new Array(26).fill(0);
  const pCount = new Array(26).fill(0);
  for (let i = 0; i < pLen; ++i) {
      ++sCount[s[i].charCodeAt() - 'a'.charCodeAt()];
      ++pCount[p[i].charCodeAt() - 'a'.charCodeAt()];
  }
  // 第一个区间
  if (sCount.toString() === pCount.toString()) {
      ans.push(0);
  }

  for (let i = 0; i < sLen - pLen; ++i) {
    // 这里的减去是为了  移除掉上一个区间留下的字符
      --sCount[s[i].charCodeAt() - 'a'.charCodeAt()];
      ++sCount[s[i + pLen].charCodeAt() - 'a'.charCodeAt()];
      console.log(sCount);
      if (sCount.toString() === pCount.toString()) {
          ans.push(i + 1);
      }
  }

  return ans;
};

总结

这道题没有做出来,解题的思路 先把p字符串变成对应的pCount数组表,然后再跟进滑动窗口,每个窗口产生一个对应的数组表sCount与pCount做比较,每个存在的字符在数组表中表示为1。重点就在这个数组表

3. 无重复字符的最长子串(通过)

题目

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

自己写的代码

var lengthOfLongestSubstring = function(s) {
  if(s.length < 1) {
    return 0
  }
  let count = 0
  for(let i=0;i<s.length;i++) {
    let j = i+1, str = s[i]
   
    while(true) {
      if(str.includes(s[j])) {
        break
      }else {
        if(j == s.length) {
          break
        }
        str = str + s[j]
        j++
      }
    }
    if(count < str.length) {
      count = str.length
    }
  }
  console.log(count);
  return count
};

使用了三层循环来完成。

看了别人的解题。


var lengthOfLongestSubstring = function(s) {
  let arr = [];
  let max = 0;
  for (let i = 0; i < s.length; i ++) {
      //如果之前存在,就删除,知道没有为止
      if(arr.indexOf(s[i]) !== -1) {
          arr.splice(0, arr.indexOf(s[i]) + 1);
      }
      //加入当前元素
      arr.push(s[i]);
      console.log(arr);
      //取最大
      max = Math.max(max, arr.length);
  }
  return max;
};

他这里只使用了 双层循环,一个值放进数组中,当遇到相同的就从头到相同的地方切掉。

总结

这道题感觉我自己的代码没有滑动窗口的味道。从别人的题解中看出一直向前走,而未回头。从前面的地方做删除。

76. 最小覆盖子串(未通过)

题目

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。

注意:

对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。 如果 s 中存在这样的子串,我们保证它是唯一的答案。

输入: s = "ADOBECODEBANC", t = "ABC"
输出: "BANC"

代码

const minWindow = (s, t) => {
    let minLen = s.length + 1;
    let start = s.length;     // 结果子串的起始位置
    let map = {};             // 存储目标字符和对应的缺失个数
    let missingType = 0;      // 当前缺失的字符种类数
    for (const c of t) {      // t为baac的话,map为{a:2,b:1,c:1}
      if (!map[c]) {
        missingType++;        // 需要找齐的种类数 +1
        map[c] = 1;
      } else {
        map[c]++;
      }
    }
    //  先右指针跑,跑到全部值都有了,就暂停,左指针移动。
    let l = 0, r = 0;                // 左右指针
    for (; r < s.length; r++) {      // 主旋律扩张窗口,超出s串就结束
      let rightChar = s[r];          // 获取right指向的新字符
      if (map[rightChar] !== undefined) map[rightChar]--; // 是目标字符,它的缺失个数-1
      if (map[rightChar] == 0) missingType--;   // 它的缺失个数新变为0,缺失的种类数就-1

      while (missingType == 0) {                // 当前窗口包含所有字符的前提下,尽量收缩窗口
        if (r - l + 1 < minLen) {    // 窗口宽度如果比minLen小,就更新minLen
          minLen = r - l + 1;        // 表示当前窗口有多大
          start = l;                 // 更新最小窗口的起点 这start还是跟在l后面的
        }
        let leftChar = s[l];          // 左指针要右移,左指针指向的字符要被丢弃
        console.log(leftChar);
        if (map[leftChar] !== undefined) map[leftChar]++; // 被舍弃的是目标字符,缺失个数+1
        if (map[leftChar] > 0) missingType++;      // 如果缺失个数新变为>0,缺失的种类+1
        l++;                          // 左指针要右移 收缩窗口
      }
    }
    if (start == s.length) return "";
    return s.substring(start, start + minLen); // 根据起点和minLen截取子串
  };

总结

在这里看到双指针 其实可以说是3指针 先左右 后面还跟这一个start,这里的关键有两点一个是使用了对象的的key来保存了某个字母出现的次数,第二个就是missingType,用来表示某个字符串是匹配完成。

剑指 Offer 59 - I. 滑动窗口的最大值(通过)

题目

给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

输入: 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

代码

var maxSlidingWindow = function(nums, k) {
 if(nums.length < 1 || !k) {
   return []
 }
 let left = 0, right = k, res = []
 for(;right<=nums.length;) {
   const arr = nums.slice(left,right)
   res.push(Math.max(...arr))
   right++
   left++
 }
 return res
};

总结

这道题的难度系数不是很大,使用了一个双指针,还有数组切片。

209. 长度最小的子数组

题目

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

输入: target = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的子数组。

代码

自己

var minSubArrayLen = function(target, nums) {
  let left = 0, right = 0, count = 0, res = Infinity
  for(;right<nums.length;) {
    while(count < target) {
      count = count + nums[right]
      right++
    }
    while(count >= target) {
      if((right - left) < res ) {
        res = right - left
      }
      count = count - nums[left]
      left++
    }
  }
  if(res == Infinity) {
    return 0
  }
  return res
};

题解代码

var minSubArrayLen = function (target, nums) {
  let left = 0,
    right = 0,
    count = 0,
    res = Infinity;
  for (; right < nums.length; ) {
    count = count + nums[right];
    console.log(count);
    while (count >= target) {
      if (right - left + 1 < res) {
        res = right - left + 1;
      }
      count = count - nums[left];
      left++;
    }
    right++;
  }
  if (res == Infinity) {
    return 0;
  }
  return res;
};

总结

题解的和我的差不多只是没有优化而已,这种就是双指针。

最后大总结

从这些题目中,学习到了技巧:

  1. 当判断字符串中的某些字符,可以使用对象来获取判断是不是有相同的。
  2. 双指针形式,但是需要知道什么时候停止
  3. 就是可以把字符转成数字 使用charCodeAt函数进行转换。其实使用前面两种结合也能实现这个功能。