您的位置:首页 > 其它

LeetCode之3. Longest Substring Without Repeating Characters

2017-08-31 21:11 411 查看

LeetCode上给的解决办法

尝试 #1 暴力方法

思考

一个个查看所有的子字符串看看是否没有相同的字符

算法

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

注意

为了列举给定字符串的所有的子字符串,我们列举这些子字符串的开始和结尾。假设开始和结尾分别是i和j。然后我们有0<=i<=j<=n。因此我们用两层嵌套循环分别是i从0到n-1和j从i+1到n,来列举s所有的子字符串。

检查一个字符串是否有相同的字符,我们可以用一个集合。对于字符串中的每一个字符进行迭代,如果没有相同的字符,那么我们把他们一个个放到集合中。在放入集合之前,我们检查这个集合是否已经包含该字符,如果包含了,返回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))。

这个n是字符串s的长度,m是字符串s的字符集的长度,

如果n> m,这个时候字符集的长度小于字符串的长度,也就是字符串中有重复的字符,所以我们取m。

如果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,
4000
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是字符集的长度。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  leetcode