LeetCode解题思路

刷题

1.刷完题后,看一下其他人的solution,受益匪浅。
2.可以按不同的topic刷题,比如数组、字符串、集合、链表等等。先做十道数组的题,接着再做十道链表的题。
3.刷题,最主要的是,学习思路。
每刷一道题,多做笔记,记住关键的点,这样第二次/第三次刷,就会有思路了。
还可以根据笔记复习。也可以记录到 GitHub。

4.优先刷Top 100。多刷几遍。
面试高频的算法题,详情见codetop(特别好用)https://codetop.cc/home
Top Interview Questions具体见: https://leetcode-cn.com/problemset/all/?listId=2ckc81c
5.兔系刷题,就是直接看答案,因为很多算法如果事先不知道是很难靠自己想出来的,知道了思路,刷第二遍的时候就不用看答案了。
或者是对着答案,敲一遍,一边敲一边理解。然后再删掉,第二天重新写一遍。
6.龟系刷题,就是自己慢慢磨,花费时间多些,难得也更难忘。甚至还可以一题多解。
7.第一次刷题,做不出来也没所谓,多刷几次。
8.个人习惯用java。当然,练习新的语言时,比如学python时也可以刷题练手。
9.LeetCode有时会报一些莫名其妙的错误,比如 error: cannot find symbol ,有可能是函数中的参数拼错了,比如没有注意大小写,或者是多了_之类的。

算法

理解一个算法最好的方法,就是在一个简单的示例图中跟踪它的轨迹。
学习算法,可以多看动态图。
详情见: https://visualgo.net/en

基础:

  • 两个数中较小的数:
        int min = Math.min(2, 3);
  • 两个数中较大的数:
        int max = Math.max(2, 3);
  • 绝对值:
        int abs = Math.abs(2 - 3);

输入输出

  • 输入输出,牛客网之类的平台有时会用到。
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
    System.out.println(scanner.next());
}

Char

  • 字符Char类型是基本类型的,比较时直接用== 。
  • 字符'a'到'z'之间的距离可以直接通过('a'-'z')获得。
  • 字符和数字的转换,可以直接减掉'0'字符得到数字。
    比如,字符'2'-'0'可以得到数字2.
  • 判断字符是否为字母:
char c='c';
boolean isLetter=Character.isLetter(c);
  • 判断字符是否为数字:
boolean digit = Character.isDigit('2');

字符串

1.获取字符串的第i个下标的字符:

char c = str.charAt(i);

2.字符串转字符数组:

String str = "adxcxvw";
char[] chars = str.toCharArray();

3.字符数组转字符串:

char[] chars = {'a','b','d'};
String str = new String(chars);

3.字符串的反转:
可以使用 StringBuilder 进行反转。

StringBuilder sb1 = new StringBuilder(str);
//倒序
sb1.reverse();
//获取下标的字符
char c = sb1.charAt(i);

4.字符串计算字符,可以先使用toCharArray()转化为字符数组。
示例题目:计算某个字符串里面A字符的次数。

public int countChar(String word) {
  if(word==null) {
     return 0;
  }
  int count=0;
  for (char c : word.toCharArray()) {
         if (c == 'A') {
                count++;
            }
   }
  return count;
}

数组

  • 排序
    不管是整型数组,还是字符数组,都可以用 Arrays.sort 排序。
Arrays.sort(arr);
  • 数组是否相同 :
Arrays.equals(charsA,charsB)

双指针(Two Pointers)

双指针有好几种。
首尾指针:第一个下标i从前往后迭代,第二个下标j从后往前迭代。不断循环,最后i和j会相等。。
快慢指针:两个指针都从头开始迭代,比如 fast(快指针),slow(慢指针),第一个指针速度是第二个指针的两倍(或者其他倍数)。
前后指针:两个指针从不同的位置开始,比如 prev(上一个节点),curr(当前节点),然后以相同的速度迭代。

示例如下:LeetCode344。反转字符数组。

public void reverseString(char[] s) {
	if(s==null) {
		return ;
	}
	//双指针,一个从数组头部开始往后遍历,一个从数组尾部往前遍历,并且不断交换两个指针的元素,最后两个指针会相遇。
	for(int i=0,j=s.length-1;i<=j;i++,j--) {
		char temp=s[i];
		s[i]=s[j];
		s[j]=temp;
	}
}

示例如下:LeetCode283。移动零到数组的末尾。

public void moveZeroes(int[] nums) {
	if (nums == null) {
		return;
	}
	//双指针,j用来记录零所在的位置
	int j = 0;
	for (int i = 0; i < nums.length; i++) {
		//如果不等于零,就将元素移到数组元素为零的位置
		if (nums[i] != 0) {
			int temp = nums[i];
			nums[i] = nums[j];
			nums[j++] = temp;
		}
	}
}

二分法

对于有序数组,可以使用二分查找的方法查找元素。
二分法,要注意边界值:

  • while 循环的条件中是 <=;
    因为不能漏掉 left == right 的情况。
  • 在二分查找的循环过程中,left = mid + 1,而 right = mid - 1。
    搜索区间[left, right]。那么当我们发现索引 mid 不是要找的 target 时,会继续查找[left, mid - 1] 或者 [mid + 1, right] 。
    因为 mid 已经搜索过,应该从搜索区间中去除。
    详情参考:https://blog.csdn.net/xiao_jj_jj/article/details/106018702
  • 示例:在有序数组中,搜索一个数,如果存在,返回其索引,否则返回 -1。
int binarySearch(int[] nums, int target) {
    int left = 0;
    int right = nums.length - 1; 

    while(left <= right) {
        int mid = (right + left) / 2;
        if(nums[mid] == target)
            return mid;
        else if (nums[mid] < target)
            left = mid + 1; 
        else if (nums[mid] > target)
            right = mid - 1; 
        }
    return -1;
}

递归

递归,自上而下,将一个大问题分解为几个小问题,然后解决这些小问题。
1.使用递归的条件:子问题需与原问题为同样的事,且规模更小;程序有停止条件。
2.递归三要素:
(1)设定函数的逻辑和返回值。
(2)设置终止条件。
(3)自身不断调用函数,设置下一级/上一级的参数。

3.递归的本质,就是栈。
递归的写法如下:
假设有一些元素x1,x2,x3...元素的计算结果,比如元素x2的计算结果由元素x1的计算结果等等决定。

public 返回类型 function(参数类型 元素param1 ) {
    //写出元素param1相关的计算逻辑,
    //写出递归程序终止的条件
    //调用方法本身,参数为"下一个元素x2"、"下下个元素x3"等等
}

示例如下:

//利用递归计算阶乘 :n*(n-1)*...*1
public  int recursion(int num){
    //忽略其他的异常参数校验,比如负数等等	
    //先写第一个元素的计算结果,以及递归程序终止的条件
    if(num==1){
        return 1;	
    }
    //调用方法本身,参数为其他的元素
    int sum=1;
    sum=num * recursion(num-1);//运用递归计算
    return sum;
}

递归典型问题: 梵塔问题(汉诺塔问题)

Set

1.HashSet无序不重复,可以用来过滤筛选掉List或数组中的重复数据。
Set的size和原来集合(可以是list,还可以是map等集合)的size相同,说明没有重复数据。也可以用contains()判断。
示例如下:

List<String> list=new ArrayList<>();
list.add("123");
list.add("456");
list.add("123");
//将list的数据放进set里面,进行去重。
Set<String> set = new HashSet<>(list);
for (String s : set) {
    //...
}
//list的size()和set的size()相等,说明没有重复数据。
if( list.size() == set.size()){

}
  • 判断是否环形链表。
    public boolean hasCycle(ListNode head) {
        if (head == null) {
            return false;
        }
        //创建Set集合,泛型为 ListNode,放入所有的链表节点
        Set<ListNode> set = new HashSet<>();
        while (head != null) {
            //每次遍历到一个节点时,判断该节点此前是否被访问过。
            if (set.contains(head)) {
                return true;
            }
            set.add(head);
            head = head.next;
        }
        return false;

    }

Map

1.Map可以用于处理两个变量的关系。一个变量作为键,另一个变量作为值。
经常可以用来统计数量。就是把出现的次数作为Map的value。

如下:

public static boolean uniqueOccurrences(int[] arr) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int num : arr) {
            //通过map来统计出现的次数
            //数组值作为key,对应的map中的value值进行加一操作。如果不存在,就设为1.
            map.put(num, map.getOrDefault(num, 0) + 1);
        }
        //如果set集合的数量和map集合的数量是一样的,说明没有重复的值。
        Set<Integer> set = new HashSet<>(map.values());
        return map.size() == set.size();
    }
  • 使用map记录字符的下标。使用 map.containsKey 判断是否重复。
    比如常见的面试题。无重复的最长子串。
    public int lengthOfSubstring(String s) {
        if (s == null) {
            return 0;
        }
        //Char的泛型是 Character,别写错了
        HashMap<Character, Integer> map = new HashMap<>();
        int max = 0;
        int left = 0;
        for (int i = 0; i < s.length(); i++) {
            //如果出现重复的字符,就重新设置滑动窗口的左下标.
            if (map.containsKey(s.charAt(i))) {
                left = Math.max(left, map.get(s.charAt(i))+1);
            }
            //使用map记录字符的下标
            map.put(s.charAt(i), i);
            //i - left + 1表示非重复子串的长度
            max = Math.max(max, i - left + 1);
        }
        return max;

    }

链表

https://blog.csdn.net/sinat_32502451/article/details/136075335

队列

  • 队列添加数据:
    Queue<Integer> queue = new ArrayDeque<>();
    queue.add(1);
  • 获取队列数据:
   Integer num= queue.poll();

先进后出。
push()入栈,pop()出栈,peek()查看但不移除.top()查看栈顶

栈的初始化有两种方式:

	//初始化栈
    Stack<String> stack=new Stack<String>();

还有另外一种是:

	//初始化栈
   Deque<Integer> stack = new LinkedList<Integer>();
  • 栈的示例:
public void stackDemo(){
    Stack<String> stack=new Stack<String>();
    stack.push("a");
    stack.push("b");
    stack.push("c");
    while(!stack.isEmpty()) {
        System.out.println(stack.pop());
    }
}
  • LeetCode20.有效的括号。
    给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
    有效字符串需满足:
    左括号必须用相同类型的右括号闭合。
    左括号必须以正确的顺序闭合。
    public boolean isValid(String s) {
        if (s==null || s.length()==0) {
            return false;
        }
        Map<String,String> map = new HashMap<>();
        //由于栈是先进后出的,因此用右括号作为key
        map.put(")","(");
        map.put("}","{");
        map.put("]","[");
        //栈
        Stack<String> stack = new Stack<>();
        for (int i=0;i<s.length();i++) {
            String c= String.valueOf( s.charAt(i) );
            //遇到左括号就入栈,后面遇到右括号就把左括号出栈。
            if(map.containsKey(c)) {
                //括号必须以正确的形式返回,因此从栈顶取出的括号,必须和字符能够配对
                if(stack.isEmpty() || !stack.peek().equals(map.get(c))) {
                    return false;
                }
                stack.pop();
            } else {
                stack.push(c);
            }
        }
        //如果是有效果括号,不断入栈出栈,最后是空字符串
        return stack.isEmpty();
    }

有些题目还会用到 单调栈。
单调栈:就是单调递增或单调减的栈。

  • LeetCode739:每日温度。

二叉树/n叉树的解题思路:
https://blog.csdn.net/sinat_32502451/article/details/136075340

回溯算法

LeetCode回溯算法的解题思路:
https://blog.csdn.net/sinat_32502451/article/details/136075345

哈希表

  • 哈希表是根据关键码值(Key Value)而直接进行访问的数据结构。它通过把关键码值映射到哈希表中的一个位置来访问记录,以加快查找的速度。这个映射函数就做散列函数,存放记录的数组叫做散列表。
  • 哈希表,相当于数组加链表。
  • 可以多看一下HashMap的源码,HashMap就是用哈希表实现的。

哈希数组

  • 创建一个数组,数组下标记录各个数字/字符,数组的值为数字/字符出现的次数。
    注意:如果数字中有负值,可以用HashMap去记录。
//将字符串p中,各个字符出现的次数记录下来。chars[]数组用于计数。
//比如p="abc",那么chars为{1,1,1,0,0....0}
//也可以说,在哈希数组chars中标记字符串p中出现的字符状况。
int[] chars = new int[26];
for (Character c : p.toCharArray()){
	chars[c-'a']++;
}

示例如下:
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。比如s = "anagram", t = "nagaram"。

public class LeetCode242 {
	public boolean isAnagram(String s, String t) {
		if (s == null && t == null) {
			return true;
		}else if (s == null ) {
			return false;
		}
		int[] counts = new int[26];
		//先用哈希数组,将26个字符出现的次数都记下来。
		//然后再遍历字符串t,将每种字符出现的次数逐次减一
		for (int i = 0; i < s.length(); i++) {
			counts[s.charAt(i) - 'a']++;
			counts[t.charAt(i) - 'a']--;
		}
		//如果两个字符串是异位字母,那么遍历过后数组counts的每一个元素都是0.
		for (int num : counts) {
			if (num != 0) {
				return false;
			}
		}
		return true;
	}
}

动态规划

动态规划的解题思路,详情见: https://blog.csdn.net/sinat_32502451/article/details/136075055

参考资料: https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/bao-li-mei-ju-dong-tai-gui-hua-chai-fen-si-xiang-b/
动态规划入门题:LeetCode70,LeetCode121,LeetCode122

查找

排序

待补充

面试常见的算法题

3. 无重复字符的最长子串
206. 反转链表
146. LRU缓存机制
215. 数组中的第K个最大元素
25. K 个一组翻转链表
15. 三数之和
53. 最大子序和
4. 手撕快速排序
21. 合并两个有序链表
1. 两数之和
5. 最长回文子串
102. 二叉树的层序遍历
33. 搜索旋转排序数组
200. 岛屿数量
121. 买卖股票的最佳时机
20. 有效的括号
141. 环形链表
236. 二叉树的最近公共祖先
46. 全排列
88. 合并两个有序数组

参考资料:

面试高频出现的 leetcode 算法题集

labuladong的算法小抄

https://leetcode-cn.com/problemset/all/?listId=2ckc81c

posted on 2019-01-05 22:50  乐之者v  阅读(1235)  评论(0编辑  收藏  举报

导航