zoukankan      html  css  js  c++  java
  • [leetcode] Valid Anagram 、 Find All Anagrams in a String

    Valid Anagram

    Given two strings s and , write a function to determine if t is an anagram of s.

    Example 1:

    Input: s = "anagram", t = "nagaram"
    Output: true
    

    Example 2:

    Input: s = "rat", t = "car"
    Output: false
    

    Note:
    You may assume the string contains only lowercase alphabets.

    Follow up:
    What if the inputs contain unicode characters? How would you adapt your solution to such case?


    分析:题目要求判断两个字符串s和t是否是颠倒字母顺序构成的,比较简单,就是字符串t是否是s中的字符组成。这个题目看了一下有两个思路,一个直接对字符串排序,然后如果相等就是true,不等就是false;第二个思路就是用HashMap来做。因为比较简单,思路也比较清晰,就讨巧用python 一句话解决一下吧

    1 class Solution(object):
    2     def isAnagram(self, s, t):
    3         """
    4         :type s: str
    5         :type t: str
    6         :rtype: bool
    7         """
    8         return sorted(s) == sorted(t)

    Find All Anagrams in a String

    Given a string s and a non-empty string p, find all the start indices of p's anagrams in s.

    Strings consists of lowercase English letters only and the length of both strings s and p will not be larger than 20,100.

    The order of output does not matter.

    Example 1:

    Input:
    s: "cbaebabacd" p: "abc"
    
    Output:
    [0, 6]
    
    Explanation:
    The substring with start index = 0 is "cba", which is an anagram of "abc".
    The substring with start index = 6 is "bac", which is an anagram of "abc".
    

    Example 2:

    Input:
    s: "abab" p: "ab"
    
    Output:
    [0, 1, 2]
    
    Explanation:
    The substring with start index = 0 is "ab", which is an anagram of "ab".
    The substring with start index = 1 is "ba", which is an anagram of "ab".
    The substring with start index = 2 is "ab", which is an anagram of "ab".

    分析:这个题目要求再s中找t的字串位置(t可以是颠倒顺序的)。其实思路很简单,一个for循环过去进行对比就行了,关键就是在于如何确定两个字符串是否是颠倒的。刚开始我想的是用异或来做,可是代码写好之后发现有问题,比如s某个字串中有"aa",但是p中有"bb",那么用异或做的话就认为是相同的了,所以不能用异或做。白浪费了半个小时去找错,无语。。。


    方法1:想到用一个list来保存p中的字符,然后去找s中的字串,如果s中某个字符再list中,就从list中删掉这个字符,最后如果list为空的话,就说明两个字符串是由相同的字符构成的了。代码如下:

     1 public List<Integer> findAnagrams(String s, String p) {
     2         List<Integer> res = new ArrayList<>();
     3         List<Character> list = new ArrayList<>();
     4         for ( char c : p.toCharArray() ) list.add(c);
     5         for ( int i = 0 ; i <= s.length() - p.length() ; i ++ ){
     6             List<Character> list_t = new ArrayList<>(list);
     7             boolean flag = true;
     8             for ( int j = i ; j < i + p.length() ; j ++ ){
     9                 if ( !list_t.contains(s.charAt(j)) ){
    10                     flag = false;
    11                     break;
    12                 }
    13                 list_t.remove((Object)s.charAt(j));
    14             }
    15             if ( flag && list_t.isEmpty() ) res.add(i);
    16         }
    17         return res;
    18     }

    结果跑到第34个用例就超时了,这说明设计题目的人可能就不想让我们借助一些简单的思路来做,还是得重新找最本质的解法。


    方法2:窗口滑动法。做字符串匹配问题时,窗口滑动是非常适用的方法,因为它省去了很多重复的比较。窗口滑动的核心还是两个指针left、right。

          定义:结果:一个窗口内的字符恰好包含p中所有的字符,叫做一个结果。

          第一步,将map初始化,形成一个p中字符与出现次数对应的map。

          第二步,扩展窗口容量。然后初始化left和right为0,形成一个大小为0的窗口,然后右边界right不断向又探查,如果right位置的字符在map中,就令对应的value减1,该过程直到窗口中包含一个完整的结果为止,可以用一个counter变量来标识,counter初始值为map.size(),然后遇到一次p中的字符就减1,注意对于每个字符counter只减一次,然后当counter==0的时候,进入下一个步骤。

          第三步,我们要做的工作是对left进行动作,缩减窗口容量。当窗口包含一个结果以后,为了进一步遍历,我们需要缩小窗口使窗口不再包含全部的p,同样,如果map[char]>=0,表明一个在p中的字符就要移除窗口,那么count ++,以此类推。 然后通过窗口长度判断是否是正确答案。

        可以说滑动窗口这种思想,关键点在于: 

    • map中存储值的意义 
    • 窗口什么时候扩展和收缩,对应于left和right值什么时候发生变化。 

        在解题的时候,首先尝试扩展窗口right,看看什么时候包含了一个结果,记录结果。然后缩小左边界left,直到窗口不在包含一个可能解!接着就可以继续扩展窗口了,以此类推。

    本题代码如下:

     1 public List<Integer> findAnagrams(String s, String p) {
     2         List<Integer> res = new ArrayList<>();
     3         Map<Character, Integer> map = new HashMap<>();
     4         for (char c : p.toCharArray()) map.put(c, map.getOrDefault(c, 0) + 1);
     5 
     6         int counter = map.size(); //代表窗口内是否包含p中全部的字符
     7         int left = 0;  //代表窗口的左边界
     8         int right = 0; //代表窗口的右边界
     9 
    10         while (right < s.length()) {
    11             char c = s.charAt(right);
    12 
    13             //下面这个代码段表示窗口右边界的扩展过程,扩展到left——right中包含p中全部的字符为止。
    14             if (map.containsKey(c)) {
    15                 map.put(c, map.get(c) - 1);
    16                 if (map.get(c) == 0) counter--;  //表示p中的字符再窗口内出现一次
    17             }
    18             right++;
    19 
    20             //下面的代码查找窗口是否满足要求,缩小窗口直到窗口不包含一个可能的解
    21             while (counter == 0) {   //counter==0的时候就是窗口内包含了p的全部字符
    22                 char left_c = s.charAt(left);
    23                 if (map.containsKey(left_c)) {
    24                     map.put(left_c, map.get(left_c) + 1);
    25                     if (map.get(left_c) > 0) counter++;  // 让右边界继续增大,寻找新的窗口
    26                 }
    27                 if (right - left == p.length()) res.add(left);
    28                 left++;
    29             }
    30         }
    31         return res;
    32     }

    在解决诸如substring这类问题时,不妨尝试想一想滑动窗口。

    leetcode上还有一些题目都可以用类似的方法来求解。先mark一下,明天再处理这些问题。

    https://leetcode.com/problems/minimum-window-substring/
    https://leetcode.com/problems/longest-substring-without-repeating-characters/
    https://leetcode.com/problems/substring-with-concatenation-of-all-words/
    https://leetcode.com/problems/longest-substring-with-at-most-two-distinct-characters/
    https://leetcode.com/problems/find-all-anagrams-in-a-string/

  • 相关阅读:
    《一起》个人进展——Day05
    《一起》个人进展——Day04
    《一起》个人进展——Day03
    《一起》个人进展——Day02
    《一起》个人进展——Day01
    this
    java_流
    input _文本框回车或者失去光标触发事件
    removeAll
    SysLog简介和java操作实例
  • 原文地址:https://www.cnblogs.com/boris1221/p/9265396.html
Copyright © 2011-2022 走看看