前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >程序员进阶之算法练习(九十)leetcode

程序员进阶之算法练习(九十)leetcode

作者头像
落影
发布2023-11-05 08:43:30
4990
发布2023-11-05 08:43:30
举报
文章被收录于专栏:落影的专栏落影的专栏

题目1 二进制求和

题目链接 题目大意: 给你两个二进制字符串 a 和 b ,以二进制字符串的形式返回它们的和。

示例 1: 输入:a = "11", b = "1" 输出:"100"

示例 2: 输入:a = "1010", b = "1011" 输出:"10101"

题目解析: 大数加法的简化版本。 注意事项: 1、逆序,从右到左操作; 2、不等长处理,长度上 a>b、a=b、a<b三种情况的考虑; 3、字符和整数转化; 4、进位考虑,累加过程,以及最后进位处理; 5、输出再重新逆序;

代码语言:javascript
复制
class Solution {
public:
    string addBinary(string a, string b) {
        string ret;
        reverse(a.begin(), a.end());
        reverse(b.begin(), b.end());
        int x = 0, y = 0, add = 0;
        while (x < a.length() || y < b.length()) {
            int temp = add;
            if (x < a.length()) {
                temp += a[x] - '0';
            }
            if (y < b.length()) {
                temp += b[y] - '0';
            }
            ret.push_back('0' + temp % 2);
            add = temp / 2;
            ++x, ++y;
        }
        if (add) {
            ret.push_back('1');
        }
        reverse(ret.begin(), ret.end());
        return ret;
    }
}leetcode;

题目2 单词搜索

题目链接 题目大意: 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

示例 : 输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED" 输出:true

题目解析: 题目给出的条件非常明确,就是按照格子进行搜索; 搜索有广度/深度优先搜索,从题目的要求来看,采用深度优先搜索能够更加方便去匹配结果和剪枝; 剪枝: 1、如果某个字符不相同,不进行搜索; 2、如果下一个字符不相同,则停止搜索;

代码语言:javascript
复制
class Solution {
    int dir[4][2] = {
        0, 1,
        0, -1,
        1, 0,
        -1, 0
    };
    bool dfs(vector<vector<char>>& board, vector<vector<char>>& vis, int x, int y, int index, string &word) {
        if (board[x][y] != word[index]) {
            return false;
        }
        if (index + 1 == word.size()) {
            return true;
        }
        for (int i = 0; i < 4; ++i) {
            int nextX = dir[i][0] + x, nextY = dir[i][1] + y;
            if (nextX < 0 || nextX >= board.size() || nextY < 0 || nextY >= board[0].size() || vis[nextX][nextY]) continue;;
            
            vis[nextX][nextY] = 1;
            if (dfs(board, vis, nextX, nextY, index + 1, word)) {
                return true;
            }
            vis[nextX][nextY] = 0;
            
        }
        return false;
    }
public:
    bool exist(vector<vector<char>>& board, string word) {
        bool ret = 0;
        vector<vector<char>> vis;
        for (int i = 0; i < board.size(); ++i) {
            vector<char> tmp(board[0].size());
            vis.push_back(tmp);
        }
        for (int i = 0; i < board.size() && !ret; ++i) {
            for (int j = 0; j < board[0].size() && !ret; ++j) {
                vis[i][j] = 1;
                ret = dfs(board, vis, i, j, 0, word);
                vis[i][j] = 0;
            }
        }
        return ret;
    }
}leetcode

题目3 Longest Consecutive Sequence

题目链接 题目大意: 给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

请你设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1: 输入:nums = [100,4,200,1,3,2] 输出:4 解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。

示例 2: 输入:nums = [0,3,7,2,5,8,4,6,0,1] 输出:9

题目解析: 把数字x看成一个点,题意变成有若干个点,点x和点x-1、点x+1能够联通,问点数最多的联通子集有几个点。 两个点集的合并,可以采用并查集的思想,每个点有个指针指向自己的父节点,初始状态是每个点指向自己; 当点x,y合并的时候,只需要把f[x]=y,相当于把x的父节点指向y。 从左到右遍历,做一遍并查集,就可以得到点数最多的集合。

接下来是如何计算集合的点数,特别是题目数据存在重复的情况。

复杂度解析: 时间复杂度是O(N) 空间复杂度是O(N)

代码语言:javascript
复制
class Solution {
public:
    int find(unordered_map<int, int> &f, int x) {
        if (f[x] == x)
            return x;
        return f[x] = find(f, f[x]);
    }
    
    int longestConsecutive(vector<int>& nums) {
        unordered_map<int, int> f;
        unordered_map<int, int> vis;
        int ans = 0;
        for (int i = 0; i < nums.size(); ++i) {
            f[nums[i]] = nums[i];
            vis[nums[i]] = 1;
        }
        
        for (int i = 0; i < nums.size(); ++i) {
            if (vis[nums[i] - 1] && find(f, nums[i] - 1) != find(f, nums[i])) { //nums[i] - 1有出现这个数字,并且两个集合的顶点不相同
                f[f[nums[i] - 1]] = f[nums[i]];
            }
            if (vis[nums[i] + 1] && find(f, nums[i] + 1) != find(f, nums[i])) {
                f[f[nums[i] + 1]] = f[nums[i]];
            }
        }
        
        unordered_map<int, int> count;
        for (int i = 0; i < nums.size(); ++i) {
            if (vis[nums[i]] && find(f, nums[i]) != nums[i]) {
                vis[nums[i]] = 0;
                count[f[nums[i]]]++;
            }
            ans = max(ans, count[f[nums[i]]] + 1);
        }
        return ans;
    }
}leetcode;

题目4 丑数 II

题目链接 题目大意: 给你一个整数 n ,请你找出并返回第 n 个 丑数 。

丑数 就是只包含质因数 2、3 or 5 的正整数。

题目解析: 如果不考虑复杂,可以从1、2、3开始枚举,不断判断数字是否满足要求;(判断方式就是用2、3、5去除,最终结果是1) 但是这样复杂度会比较高,中间会经历较多无用的数字。

考虑刚刚的判断方式,是用2、3、5去判断是否整除,那么直接用2、3、5相乘,来得到整数即可保证得到的整数都满足要求,从而过滤掉很多无用的整数。 那怎么保证是从小到大呢?

这里可以用优先队列,每次吐出队列中的最小数字; 初始状态则只需要放入2、3、5,每次拿到最小数字,则继续乘以2、3、5再放回队列中。

思考: 注意int越界的问题。

代码语言:javascript
复制
class Solution {
public:
    int nthUglyNumber(int n) {
        if (n == 1) return 1;
        priority_queue<lld, vector<lld>, greater<lld> > q;
        unordered_map<lld, lld> h;
        lld cnt = 1, cur = 1;
        q.push(1);
        while (cnt <= n) {
            cur = q.top();
            q.pop();
            ++cnt;
            if (!h[cur * 2]) {
                h[cur * 2] = 1;
                q.push(cur * 2);
            }
            
            if (!h[cur * 3]) {
                h[cur * 3] = 1;
                q.push(cur * 3);
            }
            
            if (!h[cur * 5]) {
                h[cur * 5] = 1;
                q.push(cur * 5);
            }
            
        }
        
        return cur;
    }
}lc;

题目5 LFU Cache

题目链接 题目大意: 实现LRU(最近最少使用)算法。如果有相同的使用频率,保留最近使用的(put操作不算使用)。 要求,get、put操作O(1)实现;

代码语言:javascript
复制
LFUCache cache = new LFUCache(2);
cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // returns 1
cache.put(3, 3);    // evicts key 2
cache.get(2);       // returns -1 (not found)
cache.get(3);       // returns 3.
cache.put(4, 4);    // evicts key 1.
cache.get(1);       // returns -1 (not found)
cache.get(3);       // returns 3
cache.get(4);       // returns 4

题目解析: put操作先考虑key是否存在: 1、key存在,直接更新key值; 2、key不存在, 如果cache没满,直接放入cache; 如果当前cache已满,去掉最不经常使用的key,再把key放入cache;

get操作考虑key是否存在: 1、key存在,返回key对应的值,并+1最近使用频率; 2、key不存在,返回-1;

如果使用朴素实现,list来存(key, value)对,并且在需要更新时直接去掉list节点,重新查找插入位置; 每次的操作复杂度都是O(N),并不符合要求。

优化思路: 耗时在于查找key值和插入key值;

代码语言:javascript
复制
unordered_map<int, pair<int, int> > keyMap; // key = <value, freq>
unordered_map<int, list<int>::iterator> iterMap; // 对应key的list迭代器,用于快速获得值
unordered_map<int, list<int> > freqMap; // 使用频率是map

引入三个map,分别是keyMap、iterMap和freqMap; keyMap记录对应key的value和出现频率,这样可以O(1)得到对应key值的pair(值value,出现频率freq); freqMap的索引是出现频率freq,value是链表,存储当前使用频率为freq的数字;(比如说put(1, 2),put(3, 4),两次put操作之后,key值1、3都出现了(初始出现频率=1),所以有freq=1的链表(3,4),即使freqMap[1]=(3,4); iterMap的索引是输入的key,这样可以O(1)得到对应key值的链表迭代器,方便的更新key值的出现频率;(比如说在put(1, 2),put(3, 4)之后,又出现一次get(1),此时key=1出现频率为2,所以应该挪到freqMap[2]=(1)这样,此时需要把freqMap[1]中链表的4移除,此时如果遍历会很慢,所以引入iterMap以快速访问) 这样就解决了key值的查找、插入带来的更新问题;

同时为了解决cache满了之后,需要快速定位到抛弃哪个key值的问题,我们引入整数minFreq; 因为出现频率总是从1、2、3.。这样递增,每次淘汰的时候从出现频率为minFreq的链表中选择1个即可; ps:get操作也需要更新minFreq,比如说put(1, 2)后minFreq=1,如果再出现get(1)此时minFreq=2,因为唯一的key值1已经出现了两次;仔细看代码,minFreq的更新并没有while循环,这就是额外的思考题。

代码语言:javascript
复制
class LFUCache {
public:
    int cap, curSize, minFreq; // cap是容量,curSize是当前大小,minFreq是最小的使用频率
    unordered_map<int, pair<int, int> > keyMap; // key = <value, freq>
    unordered_map<int, list<int>::iterator> iterMap; // 对应key的list迭代器,用于快速获得值
    unordered_map<int, list<int> > freqMap; // 使用频率是map
    LFUCache(int capacity) {
        cap = capacity;
        curSize = 0;
        minFreq = 0;
    }
    
    int get(int key) {
        if (keyMap.count(key) == 0) {
            return -1;
        }
        freqMap[keyMap[key].second].erase(iterMap[key]);
        ++keyMap[key].second;
        freqMap[keyMap[key].second].push_back(key);
        iterMap[key] = --freqMap[keyMap[key].second].end();
        
        if (freqMap[minFreq].size() == 0) {
            ++minFreq;
        }
        return keyMap[key].first;
    }
    
    void put(int key, int value) {
        if (cap <= 0) {
            return ;
        }
        int storgeValue = get(key);
        if (storgeValue != -1) {
            keyMap[key].first = value;
            return ;
        }
        if (curSize >= cap) {
            keyMap.erase(freqMap[minFreq].front());
            iterMap.erase(freqMap[minFreq].front());
            freqMap[minFreq].pop_front();
            --curSize;
        }
        keyMap[key] = make_pair(value, 1);
        freqMap[1].push_back(key);
        iterMap[key] = --freqMap[1].end();
        minFreq = 1;
        ++curSize;
    }
}leetcode(3);
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-11-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 题目1 二进制求和
  • 题目2 单词搜索
  • 题目3 Longest Consecutive Sequence
  • 题目4 丑数 II
  • 题目5 LFU Cache
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档