LeetCode之3. Longest Substring Without Repeating Characters

LeetCode上给的解决办法

尝试 #1 暴力方法

思考
一个个查看所有的子字符串看看是否没有相同的字符
算法
——假设我们有一个函数,boolean allUnique(String substring),这个函数在String的每个子字符串中的字符都是两两不同的时候返回True。我们可以迭代字符串S中的所有子字符串并调用allUnique这个函数。如果返回True的话,我们就更新不重复子字符串的长度。
注意

  1. 为了列举给定字符串的所有的子字符串,我们列举这些子字符串的开始和结尾。假设开始和结尾分别是i和j。然后我们有0<=i<=j<=n。因此我们用两层嵌套循环分别是i从0到n-1和j从i+1到n,来列举s所有的子字符串。
  2. 检查一个字符串是否有相同的字符,我们可以用一个集合。对于字符串中的每一个字符进行迭代,如果没有相同的字符,那么我们把他们一个个放到集合中。在放入集合之前,我们检查这个集合是否已经包含该字符,如果包含了,返回false,在循环结束后,我们返回true。
    java代码,由于复杂度较大,所以只在次贴上Leetcode上的例子,自己不再写其他语言
    [Time Limit Exceeded]
public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length();
        int ans = 0;
        for (int i = 0; i < n; i++)
            for (int j = i + 1; j <= n; j++)
                if (allUnique(s, i, j)) ans = Math.max(ans, j - i);
        return ans;
    }

    public boolean allUnique(String s, int start, int end) {
        Set<Character> set = new HashSet<>();
        for (int i = start; i < end; i++) {
            Character ch = s.charAt(i);
            if (set.contains(ch)) return false;
            set.add(ch);
        }
        return true;
    }
}

复杂度分析

  • 时间复杂度:O(n^3)

—–为了检查范围[i,j)中的所有字符是否是不重复的,我们需要扫描一遍,因此花费时间O(j-i)。对于给定的i,j花费的总时间是这里写图片描述,因此总的时间是
这里写图片描述

  • 空间复杂度

—–一个算法在运行过程中临时占用存储空间大小的量度,这是百度百科对于空间复杂度的定义,所以我们看复杂度主要是看运行过程中临时占用,比如字符s,就不属于运行过程中临时占用的,因为运行完,还是要存着s。所以应该是子字符串的长度,这个程序运行完后,就不存在了。所以应该是O(min(n,m))。

  1. 这个n是字符串s的长度,m是字符串s的字符集的长度,
  2. 如果n> m,这个时候字符集的长度小于字符串的长度,也就是字符串中有重复的字符,所以我们取m。
  3. 如果n< m,这个时候字符集的长度大于字符串的长度,也就是字符串中没有重复的字符,所以我们取n。
    第一种方法,在此,就不做具体实现了,因为本身这种方法很复杂,不想要自己记住这个。

尝试 #2 滑动窗口hashmap

算法
原始的方法是很直接的,但是非常慢,那么如何优化呢?
在原始的方法中,我们重复的检查一个子字符串看是否有重复的字符。但这个是不必要的,如果一个子字符串sij,从i到j-1是已经检查没有重复字符的,我们只需要检查s[j]是否已经在sij中就行了。

检查一个字符(给定)是否已经在子字符串中,我们可以通过双层嵌套循环扫描所有子字符串,但这样导致一个O(n^2)的时间复杂度,但是在这里我们可以改进下。

通过使用一个HashSet(java)作为一个滑动窗口,检查一个字符是否已经在子字符串中花费的时间是O(1)。

一个滑动窗口是一个虚拟的概念,这个概念被广泛的用于数组或字符串的问题。一个窗口是数组或字符串的一定范围的元素,通常被开始和结束索引来定义,注意这个窗口滑动的是偶是把两个边界滑动到某一个方向。例如,如果我们呢把[i,j)向右滑动一个,那么就变成了[i+1,j+1)。
回到问题上,我们用HashSet存储当前窗口的字符[i,j)(开始i=j),然后我们把索引j向右滑动。如果它不在,我们仍然向右滑,直到s[j]已经在HashSet中。这个时候这个时候我们就发现了最长不重复子字符串的长度。如果我们对于每一个 i,我们都滑动j,那么就可以得到结果。
java代码,由于复杂度较大,所以只在次贴上Leetcode上的例子,自己不再写其他语言
[Accepted]

这里写代码片public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length();
        Set<Character> set = new HashSet<>();
        int ans = 0, i = 0, j = 0;
        while (i < n && j < n) {
            // try to extend the range [i, j]
            if (!set.contains(s.charAt(j))){
                set.add(s.charAt(j++));
                ans = Math.max(ans, j - i);
            }
            else {
                set.remove(s.charAt(i++));
            }
        }
        return ans;
    }
}

Python自己写的

"""
slide window algorithm  range[i,j)
if s[j] not in list1, list1.append; if s[j] in list1, i jump to i+1
"""


class Solution(object):
    def lengthOfLongestSubstring(self, s):
        i, j, ans, list1 = 0, 0, 0, []
        while i < len(s) and j < len(s):
            if s[j] not in list1:
                list1.append(s[j])
                j += 1
                ans = max(ans, j - i)
            else:
                list1.remove(s[i])
                i += 1  # increase i little by little
        return ans


b = Solution()
print b.lengthOfLongestSubstring('abcabcdefa')

复杂度分析

  • 时间复杂度:O(2n)=O(n)。最坏情况下,每个字符将会被i和j访问两次。
  • 空间复杂度:O(min(m,n))。和上面的方法一样。

尝试 #3 优化后的滑动窗口hashmap

上面的解决方法需要至少2n步。事实上,可以优化为只需要n步。我们可以定义一个字符与其索引的映射,而不是用一个集合来告诉我们字符是否存在。然后我们可以立即跳过我们发现的重复的字符。
原因是如果s[j]在索引j’的范围[i,j)中有重复,我们没必要再一点一点的增加i。我们可以跳过[i,j’]或者[i,j),使i直接赋值为j’+1或j。这样讲可能比较迷惑人,我自己也是在这卡了许久。简单地说就是,如果s[j]重复,下一个i直接从j开始。
很重要,也是让我很难懂的,不懂为什么map.put (,j+1),我知道会对i产生影响,但为什么是j+1呢

java


public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length(), ans = 0;
        Map<Character, Integer> map = new HashMap<>(); // current index of character
        // try to extend the range [i, j]
        for (int j = 0, i = 0; j < n; j++) {
            if (map.containsKey(s.charAt(j))) {
                i = Math.max(map.get(s.charAt(j)), i);
            }
            ans = Math.max(ans, j - i + 1);
            map.put(s.charAt(j), j + 1);
        }
        return ans;
    }
}

C++
引用

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_map<char,int> mymap;
        unordered_map<char,int>::iterator it;
        int len = 0,i = -1;
        for(int j=0;j < s.length();j++)
        {
            /***是否有重复******/
            it = mymap.find(s.at(j));
            if(it != mymap.end())
            /*****有重复的时候,移动i*****/
                i = std::max(it->second,i);
            /****把新的字符加入*******/
            mymap[s.at(j)] = j;
            len = std::max(len,(j-i));
        }
        return len;
    }
};

Python自己写的

"""
slide window algorithm  range[i,j)
if s[j] not in list1, list1.append; if s[j] in list1, i jump to j
"""


class Solution(object):
    def lengthOfLongestSubstring(self, s):
        i, j, ans, list1 = 0, 0, 0, []
        for j in range(0, len(s)):
            if s[j] not in list1:
                list1.append(s[j])
                ans = max(ans, j - i)
            else:
                i = max(s.index(s[j]), i)  # increase i to j
        self.ans = ans
        return self.ans


b = Solution()
print b.lengthOfLongestSubstring('abcabced')

swift
后续补上
C
后续补上

复杂度分析

  • 时间复杂度:O(n)。j将会迭代n次。
  • 空间复杂度:O(m)。m是字符集的长度。

尝试 #3 数组

之前的实现都没有字符串集合s的假设,如果我们知道集合是非常的小,我们可以用整形数组代替Map作为可以直接访问的表。
常用的表格有:
int[26] for Letters ‘a’ - ‘z’ or ‘A’ - ‘Z’
int[128] for ASCII
int[256] for Extended ASCII
java


public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length(), ans = 0;
        int[] index = new int[128]; // current index of character
        // try to extend the range [i, j]
        for (int j = 0, i = 0; j < n; j++) {
            i = Math.max(index[s.charAt(j)], i);
            ans = Math.max(ans, j - i + 1);
            index[s.charAt(j)] = j + 1;
        }
        return ans;
    }
}

C++
引用

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        vector<int> mymap(255,-1);
        int len = 0,i = -1,tmp;
        for(int j=0;j < s.length();j++)
        {
            tmp = mymap[s.at(j)];
            i = std::max(tmp,i);
            mymap[s.at(j)] = j;
            len = std::max(len,(j-i));
        }
        return len;
    }
};

每个数组的初始化为-1表示没有出现重复,它不可能比i的初始值大,如果有重复的,直接覆盖,这样可以不用额外的语句判断是否出现重复。

标题

swift
C
复杂度分析

  • 时间复杂度:O(n)。j将会迭代n次。
  • 空间复杂度:O(m)。m是字符集的长度。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值