玖叶教程网

前端编程开发入门

LeetCode刷题笔记(一)(leetcode 刷题笔记)

1 题目一 两数之和

题目描述:

给定一个数组nums和一个目标值target,请你在该数组中找出和为目标值的两个整数,并返回他们的下标。你可以假设没中输入只会对应一个答案,但是,你不能重复利用这个数组中同样的元素

实例:

给定 nums = [2,7,11,15],target = 9

因为 nums[0] + nums[1] = 9

所以返回[0,1]

1.1 思路一

看到这个题目,不去想怎么设计,直接的做法是两层循环遍历,检查外层和内层循环对应的数据的值的和是否为target

具体的代码结构如下:

private int[] twoSum1(int[] nums,int target){

for(int i=0;i<nums.length;i++){

for(int j=i;j<nums.length;j++){

if(nums[i] + nums[j] == target){

return new int[]{i,j};

}

}

}

return new int[]{};

}

从上面看,两层循环,时间复杂度是O(n^2),在内存循环上进行数据的加和校验。然后将对应的下标返回。

1.2 思路二

从上面的算法来看,我初步想要如果要优化,只能将循环的次数减少,时间复杂度降低,只能拿空间换时间。最终的实现逻辑如下:

private int[] twoSum2(int[] nums,int target){

Map<Integer,Integer> maps = new HashMap<Integer, Integer>();

for(int i=0;i<nums.length;i++){

if(maps.containsKey(nums[i])){

return new int[]{maps.get(nums[i]),i};

}

maps.put(target - nums[i],i);

}

return new int[]{};

}

1.3 思路四

参考官方题解,针对思路二,可以采用两次for循环,时间复杂度为O(n)。具体的为:

class Solution {

public int[] twoSum(int[] nums, int target) {

Map<Integer, Integer> map = new HashMap<>();

for (int i = 0; i < nums.length; i++) {

map.put(nums[i], i);

}

for (int i = 0; i < nums.length; i++) {

int complement = target - nums[i];

if (map.containsKey(complement) && map.get(complement) != i) {

return new int[] { i, map.get(complement) };

}

}

throw new IllegalArgumentException("No two sum solution");

}

}

2 题目二 两数相加

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。

如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。

您可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例:

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4) 输出:7 -> 0 -> 8 原因:342 + 465 = 807

想到的第一种实现方式:

public ListNode addTwoNumbers1(ListNode l1, ListNode l2) {

return addTwoNumbers(l1,l2,0);

}

public ListNode addTwoNumbers(ListNode l1, ListNode l2,int carry){

if(l1 == null && l2 == null && carry <= 0){return null;}

int t = (l1 == null ? 0 : l1.val) + (l2 == null ? 0 : l2.val) + carry;

ListNode l = new ListNode(t % 10);

l.next = addTwoNumbers(l1 == null ? null : l1.next,l2 == null ? null : l2.next,t / 10);

return l;

}

public class ListNode{

int val;

ListNode next;

ListNode(int x) { val = x; }

}

运行结果

突然发现是最优解了,赶紧看下官方的解题思路,发现很多人想到了while循环。这里链表的查询处理,一般情况下递归或者是栈更合适。

在编写的过程中,缺少了:

if(l1 == null && l2 == null && carry <= 0){return null;}

也就是没有详细的考虑清楚跳出递归的结果。

3 题目三 无重复字符的最长子串

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

3.1 思路一

看到这个题目,每一次都是想先用最笨的方法来去先实现这个逻辑,在去考虑后期的优化

具体的代码结构如下:

public int lengthOfLongestSubstring(String s) {

List<String> list = new ArrayList<String>();

list.add(s);

return lengthOfLongestSubstring(list);

}

public int lengthOfLongestSubstring(List<String> list){

//第一步,检测子串中是否有重复的

for(String subStr : list){

if(!checkRepeat(subStr)){ //检测当前这个子串是否有重复,如果没有,则直接当前这个子串长度就是最大的不重复子串长度

return subStr.length();

}

}

List<String> subLists = new ArrayList<String>();

for(String subStr : list){

subLists.addAll(getSubStrList(subStr,1));

}

return lengthOfLongestSubstring(subLists);

}

private List<String> getSubStrList(String s, int step){

List<String> list = new ArrayList<String>();

if(s.length() <= 1){

list.add(s);

return list;

}

list.add(s.substring(0,s.length() - 1));

list.add(s.substring(1,s.length()));

return list;

}

public boolean checkRepeat(String s){

long size = s.chars().distinct().count();

return s.length() != 1 && size != s.length();

}

基本的思路就是:

1 当前字符串如果没有重复,则就是当前字符串

2 如果当前字符串有重复,则取比当前字符串短一个长度的子串出来,检查所有子串是否有重复

3 如果当前子串也还是有重复,则将当前子串的所有子串整理出来,重复2动作。

4 如果在处理过程中,有一个子串没有重复,则直接返回,当前子串的长度就是最长子串长度

对应字符串长度过长的字符串,耗时急速上升。在leetcode中直接报超时时间限制。

3.2 思路二

思路一中是根据固定每次截取子串的长度,在对每一个子串进行重复检查,如果还是重复,则在将子串长度缩短一个,进行检测。依次重复。

对上面的过程进行优化,从指定的位置开始,每次截取指定长度,对截取子串进行检测还是否有重复,如果有重复,将固定长度减小1个长度后,继续上次炒作。具体的实现为:

public int lengthOfLongestSubstring(String s) {

int max = s.length();

while(max > 0){

for(int i=0;i + max<= s.length();i++){

String subStr = s.substring(i,i + max);

if(!checkRepeat(subStr)){

return subStr.length();

}

}

max -= 1;

}

return 0;

}

在测试过程中发现,过长字符串在执行过程中耗时严重。

3.3 思路三

思路二中,采用了截取固定长度的子串来进行重复校验,如果该固定长度的子串都已经检测有重复,在将长度缩短,重新截取后计算。

上面的整个过程中,每次窗口的变化,都需要从位置的起始点开始重新截取,重新校验检测。耗时点在于每个字符串都被重复校验了多次。也就是:

假如子串:abcabcd 在max长度为7的时候,检测一次发现重复,那么其子串

abcabc

bcabcd

abcab

bcabc

cabcd

abca

bcab

cabc

都是会出现重复的,但是还是会被再次截取,然后进行重复检测。

如果还是通过从最大的子串开始检测,就会需要对子串也进行再次检测处理。如果从两个长度的字符子串截取,如果没有重复,则继续往后找,再多截取一个字符串.

这里并不能说abc这个子串的长度就是最长的非重复子串。还需要对后面的子串进行检查。具体的后续思路:

1 记录下当前检查的非重复子串的长度。如果直接丢弃掉,后面的依旧有重复的,反而没有这个长,数据就是错误

2 由于已经出现重复了,这个子串重复的位置直接剔除掉,在和后面的子串合并进行重复检查,取最长子串进行校验

3 直到最后的位置接单

具体的实现代码:

public int lengthOfLongestSubstring(String s) {

int max = 0;

StringBuffer buffer = new StringBuffer();

for(int j=0;j<s.length();j++){

buffer.append(s.charAt(j));

if(!checkRepeat(buffer.toString())){//如果不重复,则继续追加值

max = Math.max(max,buffer.length());

}else{ //重复了,找到重复的位置,并将

buffer.delete(0,buffer.indexOf(Character.toString(s.charAt(j))) + 1);

}

}

return max;

}

在leetcode上执行结果如下:

有些凄惨,对整过程在做整体的优化,因为有大量的删除字符串,重复检测。重复检测使用map或者是hashSet来做。删除重复数据只是为了保证上面的继续追加的流程检测重复使用。

到这里参考了题解,具体的方案和我后面的想法基本一致:

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;

}

滑动窗口是数组/字符串问题中常用的抽象概念。 窗口通常是在数组/字符串中由开始和结束索引定义的一系列元素的集合,即 [i, j)[i,j)(左闭,右开)。而滑动窗口是可以将两个边界向某一方向“滑动”的窗口。例如,我们将 [i, j)[i,j) 向右滑动 11 个元素,则它将变为 [i+1, j+1)[i+1,j+1)(左闭,右开)。

回到我们的问题,我们使用 HashSet 将字符存储在当前窗口 [i, j)[i,j)(最初 j = ij=i)中。 然后我们向右侧滑动索引 jj,如果它不在 HashSet 中,我们会继续滑动 jj。直到 s[j] 已经存在于 HashSet 中。此时,我们找到的没有重复字符的最长子字符串将会以索引 ii 开头。如果我们对所有的 ii 这样做,就可以得到答案

同样的对于我的思路三中的解法,都是对于第一个非重复子串下找到重复点开始的位置,逃过后,后面在进行继续的处理,这里的思路和官方给出的优化思路下一致。具体的解法如下:

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;

}

4 总结

算法的知识已经忘记了很多,在看到问题没法直接想到最优的解法,不过采用了最笨的方法,一点一点优化,最终也是最优解的方向。

每天进步一点点,对自己来说就是成长的一大步~

标签:stringsubstr 

作者:gojiuye , 分类:技术精选 , 浏览:20 , 评论:0

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言