【面试算法题总结14】空间换时间,时间换空间算法

空间换时间,时间换空间算法:

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;
    }
}
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值