2019年7月20日 - LeetCode0003

https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/submissions/

我的解法:

 1 class Solution {
 2     HashSet<Character> set = new HashSet<Character>();
 3     public int lengthOfLongestSubstring(String s) {
 4         int len = s.length();
 5         char[] string = new char[len];
 6         s.getChars(0,len,string,0);
 7         int ans = 0;
 8         //左右边界,左闭右开
 9         int left = 0,right = 0;
10         for(int i=0;i<len;++i){
11             if(set.add(string[i])){
12                 //添加成功,此字符未出现过,右边界扩张1
13                 ++right;
14                 ans = ans>(right-left)?ans:(right-left);
15             }else{
16                 //添加进集合失败,即出现了重复字符
17                 //调整左边界:从右边界现在的位置向后调整
18                 for(int j=right-1;j>=left;--j){
19                     if(string[j]==string[i]){
20                         left = j+1;
21                         break;
22                     }
23                 }
24                 ++right;
25                 ans = ans>(right-left)?ans:(right-left);
26             }
27         }
28         return ans;
29     }
30 }

维护左右边界两个数,然后每次添加进去一个新字符就更新边界,计算长度并与ans比较更新 ans

时间复杂度O(n2),因为最坏情况下每次重复的字符都是在子串的第一个的位置,如"abcdefgabcdefg",那第二个循环必须向前找很远.

空间复杂度O(n),因为用了HashSet可能比手动维护的数组的形式占得空间大一点吧

运行错误一次: 一开始省事儿从第二个字符开始检查,结果忘记了考虑空串,傻了傻了

答案错误一次: 低级错误,不说了太丢脸了(左闭右开算长度减完了我咋还+1了,爆哭)

结果:

官方题解:

https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/wu-zhong-fu-zi-fu-de-zui-chang-zi-chuan-by-leetcod/

方法1:

暴力法,代码略,考察每个子串是否满足无重复字符,会超时的.

方法2:

滑动窗口法.和我这个解法的思路是一样的,仔细想了下为什么我复杂度高然后发现改成这样就会变成O(n)了

我的方法 - 受方法2启发改:

 1 class Solution {
 2     HashSet<Character> set = new HashSet<Character>();
 3     public int lengthOfLongestSubstring(String s) {
 4         int len = s.length();
 5         char[] string = new char[len];
 6         s.getChars(0,len,string,0);
 7         int ans = 0;
 8         //左右边界,左闭右开
 9         int left = 0,right = 0;
10         for(int i=0;i<len;++i){
11             if(set.add(string[i])){
12                 //添加成功,此字符未出现过,右边界扩张1
13                 ++right;
14                 ans = ans>(right-left)?ans:(right-left);
15             }else{
16                 //添加进集合失败,即出现了重复字符
17                 //调整左边界:从左边界现在的位置向前调整
18                 for(int j=left;j<right;++j){
19                     if(string[j]==string[i]){
20                         left = j+1;
21                         break;
22                     }
23                 }
24                 ++right;
25                 ans = ans>(right-left)?ans:(right-left);
26             }
27         }
28         return ans;
29     }
30 }

这样这个方法时间复杂度就是O(2n) = O(n)的了.空间复杂度O(n)

原因在于这个虽然是两重循环,但是第二重循环每次必定是从新的地方开始,不走回头路.

如: "abcdefgabcdefg",对我的原始方法来说,当检查到第二个a的时候左边界必须向左后退到第一个a,然而下一个检查b,左边界又要向左后退到第一个b,其中[第一个b,第二个a)这些后退都是上一步检查第二个a的时候做过的了,这里在走回头路做无用功,所以浪费了时间.

对改进方法来说,检查第二个a只需要看第一个a,检查第二个b的时候只需要看第一个b,不必走回头路.

即使是对改进方法而言最坏的情况如"abcdefghijklmnnop"来说,即使检查第二个n需要从第一个a一直前进左边界直到第一个n,但是只后再也不会检查这些元素是否在左边界以左了.

体会到了"滑动窗口"这个命名的妙处.比单独的两个边界的名字有整体性.

结果:

方法3:

优化的滑动窗口,利用map而不是set,从根本上省去了第二个循环

受教,学到了的方法:

 1 class Solution {
 2     HashMap<Character,Integer> map = new HashMap<Character,Integer>();
 3     public int lengthOfLongestSubstring(String s) {
 4         int len = s.length();
 5         char[] string = new char[len];
 6         s.getChars(0,len,string,0);
 7         int ans = 0;
 8         //左右边界,左闭右开
 9         int left = 0,right = 0;
10         for(int i=0;i<len;++i){
11             if(map.containsKey(string[i])){
12                 //找到第一次出现时的位置
13                 //左边界不能变小,不能走回头路
14                 //在这个前提下把左边界变为上次出现此字符的位置再加一
15                 left = map.get(string[i]) + 1>left?map.get(string[i]) + 1:left;
16             }
17             //如果原本就有这个字符,那么这个put是更新此字符的位置,否则是加入一个新的字符并记录它的位置
18             map.put(string[i],i);
19             right = i+1;
20             ans = ans>(right - left)?ans:(right - left);
21         }
22         return ans;
23     }
24 }

left的更新中,left不可以变小很重要.

时间复杂度O(n),空间复杂度O(n)

答案错误一次: 忘记考虑left不可以变小了...

结果:

(诡异的是速度慢于上一个.可能containsKey的开销在数据量比较小的时候真的不能忽略吧)

方法4:

优化的滑动窗口法,不使用HashMap,转而使用一个数组,思路跟上面其实一样,更原始一点所以效率更优一点.

代码略

时间复杂度O(n),空间复杂度O(n)

 

转载于:https://www.cnblogs.com/IsuxizWorld/p/11219662.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值