字符串

双指针

用两个指针来定位一个子数组,其中一个指针指向数组的第1个数字,另一个指针指向数组的最后一个数字,那么两个指针之间所包含的就是一个子数组。

可以在移动这两个指针的同时,统计两个指针之间的字符串中字符出现的次数,这样可以解决很多常见的面试题,如在一个字符串中定位另一个字符串的变位词等。

由于这种类型的面试题都与统计字母出现的次数有关,我们经常使用哈希表来存储每个元素出现的次数,因此解决这种类型的面试题通常需要同时使用双指针和哈希表。

  1. 字符串中的变位词

题目:输入字符串s1和s2,如何判断字符串s2中是否包含字符串s1的某个变位词?如果字符串s2中包含字符串s1的某个变位词,则字符串s1至少有一个变位词是字符串s2的子字符串。假设两个字符串中只包含英文小写字母。例如,字符串s1为"ac",字符串s2为"dgcaf",由于字符串s2中包含字符串s1的变位词"ca",因此输出为true。如果字符串s1为"ab",字符串s2为"dgcaf",则输出为false。

由变位词的定义可知,变位词具有以下几个特点。首先,一组变位词的长度一定相同;其次,组成变位词的字母集合一定相同,并且每个字母出现的次数也相同。

/**
 * @param {string} s1
 * @param {string} s2
 * @return {boolean}
 */
var checkInclusion = function(s1, s2) {
    if(s2.length < s1.length) {
        return false;
    }

    var areAllZero = function(counts) {
        for(let i of counts) {
            if(i !== 0) {
                return false
            }
        }
        return true;
    }

    const counts = new Array(26).fill(0)

    for(let i = 0; i < s1.length; i++) {
        counts[s1.charCodeAt(i) - 97]++;
        counts[s2.charCodeAt(i) - 97]--;
    }
    if(areAllZero(counts)) {
        return true;
    }

    for (let i = s1.length; i < s2.length; ++i) {
        counts[s2.charCodeAt(i) - 97]--
        counts[s2.charCodeAt(i - s1.length) - 97]++

        if(areAllZero(counts)) {
            return true
        }
    }
    return false;
};
  1. 字符串中的所有变位词
题目:输入字符串s1和s2,如何找出字符串s2的所有变位词在字符串s1中的起始下标?假设两个字符串中只包含英文小写字母。例如,字符串s1为"cbadabacg",字符串s2为"abc",字符串s2的两个变位词"cba"和"bac"是字符串s1中的子字符串,输出它们在字符串s1中的起始下标0和5。
/**
 * @param {string} s
 * @param {string} p
 * @return {number[]}
 */
var findAnagrams = function(s1, s2) {
    if(s2.length > s1.length) {
        return [];
    }

    let result = [];

    const counts = new Array(26).fill(0);

    var areAllZero = function(counts) {
        for(let i of counts) {
            if(i !== 0) {
                return false
            }
        }
        return true;
    }

    for(let i = 0; i < s2.length; i++){
        counts[s2.charCodeAt(i)-97]++
        counts[s1.charCodeAt(i)-97]--
    }

    if(areAllZero(counts)) {
        result.push(0)
    }

    for(let i = s2.length; i < s1.length; i++) {
        counts[s1.charCodeAt(i)-97]--
        counts[s1.charCodeAt(i-s2.length)-97]++

        if(areAllZero(counts)) {
            result.push(i-s2.length+1)
        }
    }

    return result;
};
  1. 不含重复字符的最长子字符串
题目:输入一个字符串,求该字符串中不含重复字符的最长子字符串的长度。例如,输入字符串"babcca",其最长的不含重复字符的子字符串是"abc",长度为3。
/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
    if(s.length == 0) return 0
    if(s.length == 1) return 1

    const map = new Map();
    let max = 0
    // 双指针
    let left = 0
    let right = 0

    var areAllZero = function(counts) {
        for(let i of counts) {
            if(i[1] === 2) {
                return false
            }
        }
        return true;
    }

    while(right < s.length) {
        if(map.get(s.charAt(right)) !== 1) {
            map.set(s.charAt(right), (map.get(s.charAt(right)) || 0) + 1)
            right++
            if(map.get(s.charAt(right)) === 1) {
                max = Math.max(right - left, max)
            }
        } else {
            map.set(s.charAt(left), (map.get(s.charAt(left)) || 0) - 1)
            left++
        }
    }
    max = Math.max(right - left, max)

    return max;
};
  1. 包含所有字符的最短字符串
输入两个字符串s和t,请找出字符串s中包含字符串t的所有字符的最短子字符串。例如,输入的字符串s为"ADDBANCAD",字符串t为"ABC",则字符串s中包含字符'A'、'B'和'C'的最短子字符串是"BANC"。如果不存在符合条件的子字符串,则返回空字符串""。如果存在多个符合条件的子字符串,则返回任意一个。
var minWindow = (s, t) => {
    const charToCount = new Map();
    
    for(let ch of t) {
        charToCount.set(ch, (charToCount.get(ch) || 0) + 1)
    }
    let count = charToCount.size;
    let start = 0, end = 0, minStart = 0, minEnd = 0;
    let minLength = Infinity;
    while(end < s.length || (count == 0) && end == s.length) {
        if(count > 0) {
            let endCh = s.charAt(end);
            if(charToCount.get(endCh)!== undefined) {
                charToCount.set(endCh, charToCount.get(endCh)-1)
                if (charToCount.get(endCh) === 0) {
                    count--
                }
            }
        } else {
            if (end - start < minLength) {
                minLength = end - start
                minStart = start
                minEnd = end
            }
            let startCh = s.charAt(start);
            if(charToCount.get(startCh)!==undefined) {
                charToCount.set(startCh, charToCount.get(startCh) + 1)
                if(charToCount.get(startCh) == 1) {
                    count++
                }
            }
            start++
        }
    }

    return minLength < Infinity ? s.substring(minStrat, minEnd) : "";
}

上述代码中只有一个while循环,用来把两个变量从0增加到字符串s的长度。如果字符串的长度是n,那么时间复杂度就是O(n)。可以使用一个哈希表来统计每个字符出现的次数。哈希表的键为字符,假设字符串中只有英文字母,那么哈希表的大小不会超过256,辅助空间的大小不会随着字符串长度的增加而增加,因此空间复杂度是O(1)。

回文字符串

  1. 有效的回文
给定一个字符串 s ,验证 s 是否是 回文串 ,只考虑字母和数字字符,可以忽略字母的大小写。
/**
 * @param {string} s
 * @return {boolean}
 */
var isPalindrome = function(s) {
    let l = 0;
    let r = s.length - 1;
    const reg = /[a-zA-Z0-9]/
    while(l <= r) {
        if(!reg.test(s.charAt(l))) {
            l++
            continue
        }
        if(!reg.test(s.charAt(r))) {
            r--
            continue
        }
        if(s.charAt(r).toLocaleLowerCase() !== s.charAt(l).toLocaleLowerCase()) {
            return false
        } else {
            l++
            r--
        }
    }
    return true
};
  1. 最多删除一个字符得到回文
给定一个非空字符串 s,请判断如果 最多 从字符串中删除一个字符能否得到一个回文字符串。
/**
 * @param {string} s
 * @return {boolean}
 */
var validPalindrome = function (s) {
  let low = 0,
    high = s.length - 1;
  const valid = (low, high) => {
    for (let i = low, j = high; i < j; i++, j--) {
      if (s[i] != s[j]) return false;
    }
    return true;
  };
  while (low < high) {
    if (s[low] == s[high]) {
      low++;
      high--;
    } else {
      return valid(low + 1, high) || valid(low, high - 1);
    }
  }
  return true;
};

我没有考虑到2种情况都让他执行

  1. 回文子字符串的个数
题目:给定一个字符串,请问该字符串中有多少个回文连续子字符串?例如,字符串"abc"有3个回文子字符串,分别为"a"、"b"和"c";而字符串"aaa"有6个回文子字符串,分别为"a"、"a"、"a"、"aa"、"aa"和"aaa"。

前面都是从字符串的两端开始向里移动指针来判断字符串是否是一个回文,其实也可以换一个方向从字符串的中心开始向两端延伸。如果存在一个长度为m的回文子字符串,则分别再向该回文的两端延伸一个字符,并判断回文前后的字符是否相同。如果相同,就找到了一个长度为m+2的回文子字符串。

/**
 * @param {string} s
 * @return {number}
 */
var countSubstrings = function(s) {

    if(s == null || s.length == 0) { return 0 }

    let count = 0;
    for(let i = 0; i < s.length; i++) {
        count += countPalindrome(s, i, i)
        count += countPalindrome(s, i, i + 1)
    }
    return count
};

var countPalindrome = function(s, start, end) {
    let count = 0;
    while(start >= 0 && end < s.length && s.charAt(start) == s.charAt(end)) {
        count++
        start--
        end++
    }
    return count;
}

上述解法仍然需要两个嵌套的循环,因此时间复杂度是O(n2)。该解法只用到了若干变量,其空间复杂度是O(1)。

总结


看见了
876 声望16 粉丝

前端开发,略懂后台;


引用和评论

1 篇内容引用
0 条评论