空间换时间,时间换空间算法:
1 空间换时间:使用HashMap保存数据
例题1:两数之和
解法1:时间优先(HashMap)
时间复杂度:O(n)
空间复杂度: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){
int newTarget=target-nums[i];
if(map.containsKey(newTarget)){
return new int[]{map.get(newTarget),i};
}
map.put(nums[i],i);
}
return new int[2];
}
}
解法2:空间优先(遍历所有情况)
时间复杂度:O(n^2)
空间复杂度:O(1)
class Solution {
public int[] twoSum(int[] nums, int target) {
for(int i=0;i<nums.length;++i){
for(int j=i+1;j<nums.length;++j){
int sum=nums[i]+nums[j];
if(sum==target){
return new int[]{i,j};
}
}
}
return new int[2];
}
}
不能先排序,再左右指针。这样排序的时候,原数据的下标数据就没了
例题2:和为 K 的子数组
HashMap保存前缀和,以及其出现次数
class Solution {
public int subarraySum(int[] nums, int k) {
int result=0;
Map<Integer,Integer> map=new HashMap<Integer,Integer>();
int sum=0;
map.put(0, 1);
for(int i=0;i<nums.length;++i){
sum+=nums[i];
int need=sum-k;
if(map.containsKey(need)){
result+=map.get(need);
}
map.put(sum,map.getOrDefault(sum,0)+1);
}
return result;
}
}
例题3:路径总和 III
例题2的二叉树版本:
主要学习的题解:https://leetcode-cn.com/problems/path-sum-iii/solution/dui-qian-zhui-he-jie-fa-de-yi-dian-jie-s-dey6/
1 前缀和:一个节点的前缀和就是该节点到根之间的路径和
2 HashMap的key是前缀和, value是该前缀和的节点数量,记录数量是因为有出现复数路径的可能
3 本质还是空间换时间,保存各种前缀和出现的次数。
即:要寻找的前缀和=根节点到当前节点的路径和-targetSum
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int wholeTargetSum;
Map<Integer,Integer> map;
public int pathSum(TreeNode root, int targetSum) {
wholeTargetSum=targetSum;
map=new HashMap<>();
map.put(0,1); //十分重要的一行,不然就不能从根节点开始算了
return dfs(root,0);
}
public int dfs(TreeNode root,int pre){
if(root==null){
return 0;
}
int pathSum=pre+root.val; //获取节点到当前节点root的路径和pathSum
int needPrefix=pathSum-wholeTargetSum; //得到需要的前缀和needPrefix
int count=map.getOrDefault(needPrefix,0); //得到needPrefix对应次数
/*添加该前缀和到map中,给子节点使用
这行代码不能放在 int count=map.getOrDefault(needPrefix,0); 之前,因为路径不能不包括任何节点
*/
map.put(pathSum,map.getOrDefault(pathSum,0)+1);
count+=dfs(root.left,pathSum);
count+=dfs(root.right,pathSum);
map.put(pathSum,map.get(pathSum)-1); //去除这个前缀和对应的次数
return count;
}
}
例题4:将链表切成三段的最小代价
如输入[1,2,3,4,5],输出6
public static int solution(int[] A) {
int n = A.length;
int[] min = new int[n];
min[1] = A[1];
for (int i = 2; i < n; i++) {
min[i] = Math.min(min[i - 1], A[i]);
}
int ans = Integer.MAX_VALUE;
for (int i = 3; i <= n - 2; i++) {
ans = Math.min(ans, A[i] + min[i - 2]);
}
return ans;
}
例题5:数组中重复的数字
这题以空间换时间,还用的是本来就有的空间,真的是省到了极值
class Solution {
public int findRepeatNumber(int[] nums) {
for(int i=0;i<nums.length;++i){
while(nums[i]!=i){
if(nums[nums[i]]==nums[i]){
return nums[i];
}
int temp=nums[nums[i]];
nums[nums[i]]=nums[i];
nums[i]=temp;
}
}
return -1;
}
}
例题7:丑数
解法1:低空间消耗
class Solution {
public boolean isUgly(int n) {
if(n<=0){
return false;
}
while(n%2==0){
n/=2;
}
while(n%3==0){
n/=3;
}
while(n%5==0){
n/=5;
}
return n==1;
}
public int nthUglyNumber(int n) {
int count=0;
int result=0;
while(count<n){
++result;
if(isUgly(result)){
++count;
}
}
return result;
}
}
解法2:低时间消耗
class Solution {
public int nthUglyNumber(int n) {
int[] uglyNum=new int[n];
uglyNum[0]=1;
int p2=0,p3=0,p5=0;
for(int i=1;i<n;++i){
uglyNum[i]=Math.min(uglyNum[p2]*2,Math.min(uglyNum[p3]*3,uglyNum[p5]*5));
if(uglyNum[i]==uglyNum[p2]*2){
++p2;
}
if(uglyNum[i]==uglyNum[p3]*3){
++p3;
}
if(uglyNum[i]==uglyNum[p5]*5){
++p5;
}
}
return uglyNum[n-1];
}
}
例题8:第一个只出现一次的字符
class Solution {
public char firstUniqChar(String s) {
Map<Character,Integer> map=new HashMap<>();
for(int i=0;i<s.length();++i){
map.put(s.charAt(i),map.getOrDefault(s.charAt(i),0)+1);
}
for(int i=0;i<s.length();++i){
if(map.get(s.charAt(i))==1){
return s.charAt(i);
}
}
return ' ';
}
}
例题9:LRU 缓存
哈希链表:双向链表+map做到O(1)查找和删除添加
class LRUCache {
int cap;
LinkedHashMap<Integer,Integer> cache=new LinkedHashMap<>();
public LRUCache(int capacity) {
this.cap=capacity;
}
public int get(int key) {
if(!cache.containsKey(key)){
return -1;
}
//key变为最近使用
makeRecently(key);
return cache.get(key);
}
public void put(int key, int value) {
if(cache.containsKey(key)){
//修改key值
cache.put(key,value);
//将key变为最近使用
makeRecently(key);
return;
}
if(cache.size()>=this.cap){
//链表头部就是最久未使用的key
int oldestKey=cache.keySet().iterator().next();
cache.remove(oldestKey);
}
//将最新的key添加到链表尾部
cache.put(key,value);
}
private void makeRecently(int key){
int val=cache.get(key);
//删除key,重新插入到队尾
cache.remove(key);
cache.put(key,val);
}
}
例题10:LFU 缓存
淘汰访问频次最低的数据,如果有多条,需要淘汰最旧的数据
class LFUCache {
HashMap<Integer,Integer> keyToVal;
HashMap<Integer,Integer> keyToFreq;
HashMap<Integer,LinkedHashSet<Integer>> freqToKeys;
//记录最小的频次
int minFreq;
int cap;
public LFUCache(int capacity) {
keyToVal=new HashMap<>();
keyToFreq=new HashMap<>();
freqToKeys=new HashMap<>();
this.cap=capacity;
this.minFreq=0;
}
public int get(int key) {
if(!keyToVal.containsKey(key)){
return -1;
}
//增加key对应的freq
increaseFreq(key);
return keyToVal.get(key);
}
public void put(int key, int value) {
if(this.cap<=0)return;
//如果key已存在,修改对应的val即可
if(keyToVal.containsKey(key)){
keyToVal.put(key,value);
increaseFreq(key);
return;
}
//如果key不存在,需要插入
if(this.cap<=keyToVal.size()){
removeMinFreqKey();
}
//插入key和value,对应的freq为1
keyToVal.put(key,value);
keyToFreq.put(key,1);
freqToKeys.putIfAbsent(1,new LinkedHashSet<>());
freqToKeys.get(1).add(key);
this.minFreq=1;
}
private void removeMinFreqKey(){
//freq最小的key列表
LinkedHashSet<Integer> keyList=freqToKeys.get(this.minFreq);
//其中最先被插入的那个key就是该被淘汰的key
int deletedKey=keyList.iterator().next();
keyList.remove(deletedKey);
if(keyList.isEmpty()){
freqToKeys.remove(this.minFreq);
}
//更新KV表
keyToVal.remove(deletedKey);
//更新KF表
keyToFreq.remove(deletedKey);
}
private void increaseFreq(int key){
int freq=keyToFreq.get(key);
//更新KF表
keyToFreq.put(key,freq+1);
//更新FK表
//将key从freq对应的列表中删除
freqToKeys.get(freq).remove(key);
//将key加入freq+1对应的列表中
freqToKeys.putIfAbsent(freq+1,new LinkedHashSet<>());
freqToKeys.get(freq+1).add(key);
//如果freq对应的列表空了,移除这个freq
if(freqToKeys.get(freq).isEmpty()){
freqToKeys.remove(freq);
//如果这个freq恰好是minFreq,更新minFreq
if(freq==this.minFreq){
++this.minFreq;
}
}
}
}
/**
* Your LFUCache object will be instantiated and called as such:
* LFUCache obj = new LFUCache(capacity);
* int param_1 = obj.get(key);
* obj.put(key,value);
*/
例题11:字母异位词分组
优雅永不过时
解法1:排序
时间复杂度:O(nklogk)。k 是strs 中的字符串的的最大长度
空间复杂度:O(nk)
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String,List<String>> map=new HashMap<>();
for(String str:strs){
char[] chars=str.toCharArray();
Arrays.sort(chars);
String key=String.valueOf(chars);
List<String> value=map.getOrDefault(key,new ArrayList<>());
value.add(str);
map.put(key,value);
}
return new ArrayList<>(map.values());
}
}
解法2:计数
时间复杂度:O(n(k+∣Σ∣))。k 是strs 中的字符串的的最大长度。Σ 是字符集,在本题中字符集为所有小写字母,∣Σ∣=26。
空间复杂度:O(nk)
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String,List<String>> map=new HashMap<>();
for(String str:strs){
//得到字符及其次数key
int[] counts=new int[26];
char[] chars=str.toCharArray();
for(char c:chars){
++counts[c-'a'];
}
StringBuilder tempKey=new StringBuilder();
for(int i=0;i<26;++i){
if(counts[i]!=0){
tempKey.append((char)('a'+i));
tempKey.append(counts[i]);
}
}
String key = tempKey.toString();
//与解法1相同的部分
List<String> value=map.getOrDefault(key,new ArrayList<>());
value.add(str);
map.put(key,value);
}
return new ArrayList<>(map.values());
}
}
例题12:买卖股票的最佳时机
class Solution {
public int maxProfit(int[] prices) {
int n=prices.length;
int minNum=prices[0];
int result=0;
for(int i=1;i<n;++i){
if(prices[i]<minNum){
minNum=prices[i];
}else if(result<prices[i]-minNum){
result=prices[i]-minNum;
}
}
return result;
}
}
例题13:最长连续序列
class Solution {
public int longestConsecutive(int[] nums) {
Set<Integer> set=new HashSet<>();
for(Integer num:nums){
set.add(num);
}
int result=0;
for(Integer num:set){
if(!set.contains(num-1)){
int currentNum=num;
int currentResult=1;
while(set.contains(currentNum+1)){
++currentNum;
++currentResult;
}
if(currentResult>result){
result=currentResult;
}
}
}
return result;
}
}