「常见解法总结」
1「哈希法」
使用了哈希表不能存储重复数据的特性进行元素的暂存,常用于字符串和数组,可用于查找唯一元素和查重
HashMap的基本使用
HashMap 类位于 java.util 包中,使用前需要引入它,语法格式如下:
import java.util.HashMap;创建一个HashMap对象
HashMap<String,Object> hashMap = new HashMap<>(); Map<String,Object> hashMap = new HashMap<>();常用方法
import java.util.HashMap; public class Test { public static void main(String[] args) { // 创建 HashMap 对象 Sites HashMap<Integer, String> Sites = new HashMap<Integer, String>(); // 1、添加键值对 Sites.put(1, "Google"); Sites.put(2, "Runoob"); Sites.put(3, "Taobao"); Sites.put(4, "Zhihu"); // 2、使用 get(key) 方法来获取 key 对应的 value System.out.println(Sites.get(3)); // 3、使用 remove(key) 方法来删除 key 对应的键值对 Sites.remove(4); // 4、使用 size()方法计算元素数量 System.out.println(Sites.size()); // 5、keySet() 方法获取所有key,values() 方法获取所有value // 6、isEmpty() 方法用于检查该 HashMap 是否为空 // 7、containsKey() 方法检查 hashMap 中是否存在指定的 key 对应的映射关系 if(sites.containsKey(1)) { System.out.println("key 为 1 存在于 sites 中"); } // 8、replace() 方法替换 hashMap 中是指定的 key 对应的 value sites.replace(1, "Google", "Wiki"); // 9、getOrDefault() 方法获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值 String value1 = sites.getOrDefault(1, "Not Found"); } }
HashSet的基本使用
HashSet 基于 HashMap 来实现的,是一个无序的,不允许有重复元素,但允许有null值的集合。
常见方法
import java.util.HashSet; public class Test { public static void main(String[] args) { HashSet<String> sites = new HashSet<String>(); // 1、使用 add() 方法添加元素 sites.add("Google"); sites.add("Runoob"); sites.add("Taobao"); sites.add("Zhihu"); sites.add("Runoob"); // 重复的元素不会被添加 System.out.println(sites); // 2、使用 contains() 方法来判断元素是否存在于集合当中 System.out.println(sites.contains("Taobao")); // 3、使用 remove() 方法来删除集合中的元素,成功返回 true,否则为 false sites.remove("Taobao"); // 4、使用size() 方法计算元素数量 System.out.println(sites.size()); // 5、使用for-each迭代元素 for (String i : sites) { System.out.println(i); } } }
2「字符串常用方法」
String类
- toCharArray():将字符串转换为字符数组,如char[] ch = str.toCharArray();
- charAt(index):获得字符串中指定索引的字符,index从0开始
- substring(int beginIndex, int endIndex):返回从beginIndex(从0开始)到endIndex(不包括)的子字符串
- valueOf(char[] data):返回 char数组的字符串表示形式
- valueOf(char[] data,int offset, int count):返回 char 数组参数的特定子数组的字符串表示形式。
- valueOf(int i):返回 int 参数的字符串表示形式。
- indexOf(int ch):返回指定字符在此字符串中第一次出现的索引
- indexOf(int ch, int fromindex): 同上, 从指定索引开始搜索
- indexOf(String str):返回子串在此字符串中第一次出现的索引
- indexOf(String str, int fromindex):同上,从指定索引开始搜索
- length():返回字符串的长度
- 比较:== , != 判断的是两个字符串地址是否相等,equals()方法是直接比较两个字符串对象的内容,相等返回 true,否则返回false,如str1.equals(str2)
StringBuffer类和StringBuilder类
Ø public StringBuffer append(String s):将指定的字符串追加到此字符序列。
Ø public StringBuffer reverse():将此字符序列用其反转形式取代
Ø delete(int start, int end):移除此序列从start到end-1的字符串
Ø int capacity():返回当前容量。
Ø int length():返回长度(字符数)
3「双指针法」
严格的来说,双指针只能说是是算法中的一种技巧。
双指针指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个相同方向(快慢指针)或者相反方向(对撞指针)的指针进行扫描,从而达到相应的目的。
快慢指针
快慢指针是两个指针从同一侧开始遍历数组,将这两个指针分别定义为快指针(fast)和慢指针(slow),两个指针以不同的策略移动,如快指针每次增长两个,慢指针每次增长一个。直到两个指针的值相等(或其他特殊条件)为止。
常见应用1:判断链表是否有环
/** * Definition for singly-linked list. * class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */ public class Solution { public boolean hasCycle(ListNode head) { ListNode p1 = head; //慢指针 ListNode p2 = head; //快指针,两个指针从同一节点出发 while(p2 != null && p2.next != null){ p1 = p1.next; //慢指针每次移动一位 p2 = p2.next.next; //快指针每次移动两位 if(p1 == p2){ //相遇则说明有环 return true; } } return false; } }
常见应用2:求链表中点
ListNode fast = head; ListNode slow = head; while(fast.next != null && fast.next.next != null){//快慢指针求中点 fast = fast.next.next; slow = slow.next; }
对撞指针
对撞指针是指在数组或字符串中,将指向最左侧的索引定义为左指针(left),最右侧的定义为右指针(right),然后从两头向中间进行数组或字符串进行遍历
滑动窗口
参考资料:滑动窗口算法基本原理与实践
两个指针,一前一后组成滑动窗口,然后对窗口内的元素进行计算,一般用于字符串匹配、子数组等问题
基本思路
- 初始化 left = right = 0,把索引闭区间 [left, right] 称为一个「窗口」
- 先不断地增加 right 指针扩大窗口 [left, right],直到窗口中的元素符合条件(相当于在寻找一个可行解)
- 停止增加 right,转而不断增加 left 指针缩小窗口 [left, right],直到窗口中的元素不再符合要求。同时,每次增加 left,都更新一轮结果(优化这个可行解,最终找到最优解)
例题1:LC 209. 长度最小的子数组
class Solution { public int minSubArrayLen(int target, int[] nums) { int min = nums.length+1; int left = 0; int right = 0; int sum = 0; while(right < nums.length){ //移动右指针,累加元素值,寻找满足条件的区间 sum += nums[right]; while(sum >= target){ //找到满足条件的区间,就移动左指针,减去left所指值,寻找满足条件的最小区间 min = Math.min(min, right-left+1); sum -= nums[left]; left++; } right++; } //不存在符合条件的子数组就返回0 return min == nums.length+1 ? 0 : min; } }
例题2:LC 3. 无重复字符的最长子串题目地址:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/
题目描述
给定一个字符串s,请你找出其中不含有重复字符的 最长子串 的长度。
输入: s = "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
class Solution { public int lengthOfLongestSubstring(String s) { if (s.length() == 0) return 0; HashMap<Character, Integer> map = new HashMap<Character, Integer>(); int max = 0; int left = 0; for(int i = 0; i < s.length(); i++){ //移动右指针扩大窗口,当遇到出现过的字符时,取出该重复字符的下标加一与left比较取最大值赋给left //其实就是将left移动到重复字符的后面,因为该位置的字符重复了,需要将其前面的字符都移出窗口 if(map.containsKey(s.charAt(i))){ left = Math.max(left, map.get(s.charAt(i)) + 1); } //不存在就插入,已存在的会替换为新的value值 map.put(s.charAt(i), i); //更新当前的最长子串 max = Math.max(max, i-left+1); } return max; } }
4「分治」
何为分治: 分而治之,即把一个复杂的大问题分解成两个或者是更多小问题,递归地解决划分后的子问题,分别对各个小问题给出解决方法,再将结果合并从而高效地解决问题。
注意满足以下条件:
分:将一个复杂的问题分解成多个相同或者相似的子问题,再将子问题逐步分解
治:将最后得到的简单子问题求解
合:将所有问题的解合并起来,得到初始问题的答案
注意满足以下条件:
分:将一个复杂的问题分解成多个相同或者相似的子问题,再将子问题逐步分解
治:将最后得到的简单子问题求解
合:将所有问题的解合并起来,得到初始问题的答案
分治算法可以分三步走:分解 -> 解决(触底) -> 合并(回溯)
- 分解原问题为结构相同的子问题。
- 分解到某个容易求解的边界之后,进行递归求解。
- 将子问题的解合并成原问题的解。
- 快速排序
- 归并排序
5「二分法」
二分法是一个非常高效的算法,常用于查找。
在用二分法进行查找时,查找对象的数组必须是有序的,即各数组元素的次序是按其值的大小顺序存储的。其基本思想是先确定待查数据的范围(可用 [left,right] 区间表示),然后逐步缩小范围直到找到或找不到该记录为止。具体做法是:先取数组中间位置(mid=(left+right)/2)的数据元素与给定值比较。若相等,则查找成功;否则,若给定值比该数据元素的值小(或大),则给定值必在数组的前半部分[left,mid-1](或后半部分[mid+1,right]),然后在新的查找范围内进行同样的查找。如此反复进行,直到找到数组元素值与给定值相等的元素或确定数组中没有待查找的数据为止。因此,二分查找每查找一次,或成功,或使查找数组中元素的个数减少一半,当查找数组中不再有数据元素时,查找失败。
二分法查找是一种非常高效的搜索方法,主要原理是每次搜索可以抛弃一半的值来缩小范围。其时间复杂度是O(log2n),一般用于对普通搜索方法的优化。
二分法的适用情况一般满足以下几点:
(1)该数组数据量巨大,需要对处理的时间复杂度进行优化;
(2)该数组已经排序;
(3)一般要求找到的是某一个值或一个位置。