力扣
841.钥匙和房间
题目描述
有 N 个房间,开始时你位于 0 号房间。每个房间有不同的号码:0,1,2,...,N-1,并且房间里可能有一些钥匙能使你进入下一个房间。
在形式上,对于每个房间 i 都有一个钥匙列表 rooms[i],每个钥匙 rooms[i][j] 由 [0,1,...,N-1] 中的一个整数表示,其中 N = rooms.length。 钥匙 rooms[i][j] = v 可以打开编号为 v 的房间。
最初,除 0 号房间外的其余所有房间都被锁住。
你可以自由地在房间之间来回走动。
如果能进入每个房间返回 true,否则返回 false。
示例 1:
输入: [[1],[2],[3],[]]
输出: true
解释:
我们从 0 号房间开始,拿到钥匙 1。
之后我们去 1 号房间,拿到钥匙 2。
然后我们去 2 号房间,拿到钥匙 3。
最后我们去了 3 号房间。
由于我们能够进入每个房间,我们返回 true。
示例 2:
输入:[[1,3],[3,0,1],[2],[0]]
输出:false
解释:我们不能进入 2 号房间。
提示:
1 <= rooms.length <= 1000
0 <= rooms[i].length <= 1000
所有房间中的钥匙数量总计不超过 3000。
分析
根据题解学习,该题目为孤岛问题,若有孤岛,则返回false,若无孤岛,则返回true。而遍历过程,使用bfs与dfs均可。
bfs解法
首先,bfs是广度优先遍历,使用深度优先遍历到每个节点,并用vis来记录是否遍历过。思路是:
- 首先找到 0 号房间,把所有 0 号房间的钥匙都开一遍。
- 进入刚刚开过的房间,再把它们房间里的钥匙再开一遍。
- 重复以往,层层递进,直到找不到符合要求的节点。
根据大佬的解答,队列和bfs是经常一起出现的。
class Solution { //广度优先搜索 bool bfs(const vector<vector<int>>& rooms){ vector<int> visited(rooms.size(), 0); // 标记房间是否被访问过 visited[0] = 1; // 0 号房间开始,遍历过,设为1 queue<int> que; //队列作用为如果没有遍历过,则压入队列,下一次循环遍历 que.push(0); // 0 号房间开始 // 广度优先搜索的过程 while (!que.empty()) { int key = que.front(); que.pop(); vector<int> keys = rooms[key]; for (int key : keys) { if (!visited[key]) { que.push(key); visited[key] = 1; } } } // 检查房间是不是都遍历过了 for (int i : visited) { if (i == 0) return false; } return true; } public: bool canVisitAllRooms(vector<vector<int>>& rooms) { return bfs(rooms); } };
dfs解法
dfs是深度优先搜索,同样使用vis来存储是否访问过,思路为:
- 先找第0个房间的第一个钥匙
- 进入那个房间,再找它的第一个钥匙
- 重复以往,直到没钥匙了,那么退回刚刚的房间
- 找刚刚房间的第二把钥匙
- 重复搜索,直到所有房间遍历完成
class Solution { private: void dfs(int key, const vector<vector<int>>& rooms, vector<int>& visited) { if (visited[key]) { return; }//若访问过了,则返回上一层 visited[key] = 1;//若没有访问过,则将该房间的vis变为1 vector<int> keys = rooms[key];//该房间里的钥匙能打开哪些锁 for (int key : keys) { // 深度优先搜索遍历 dfs(key, rooms, visited);//递归方式将可访问到的房间位置变为1 } } public: bool canVisitAllRooms(vector<vector<int>>& rooms) { vector<int> visited(rooms.size(), 0); dfs(0, rooms, visited); //检查是否都访问到了 for (int i : visited) { if (i == 0) return false;//如果有0,则为孤岛 } return true;//遍历后没有0,则都可以进入 } };
4、寻找两个有序数组的中位数
题目描述
给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。
请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。
示例 1:
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5
分析
因为规定了时间复杂度,所以合并后取值不可用(时间复杂度为O(M+N)),所以使用二分查找,是奇数则取中间值,是偶数则取中间两个数和的二分之一。
题解中的图:
二分查找法
class Solution { public: int getKthElement(const vector<int>& nums1, const vector<int>& nums2, int k) { /* 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较 * 这里的 "/" 表示整除 * nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个 * nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个 * 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个 * 这样 pivot 本身最大也只能是第 k-1 小的元素 * 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组 * 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组 * 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数 */ int m = nums1.size(); int n = nums2.size(); int index1 = 0, index2 = 0; while (true) { // 边界情况 if (index1 == m) { return nums2[index2 + k - 1]; } if (index2 == n) { return nums1[index1 + k - 1]; } if (k == 1) { return min(nums1[index1], nums2[index2]); } // 正常情况 int newIndex1 = min(index1 + k / 2 - 1, m - 1); int newIndex2 = min(index2 + k / 2 - 1, n - 1); int pivot1 = nums1[newIndex1]; int pivot2 = nums2[newIndex2]; if (pivot1 <= pivot2) { k -= newIndex1 - index1 + 1; index1 = newIndex1 + 1; } else { k -= newIndex2 - index2 + 1; index2 = newIndex2 + 1; } } } double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) { int totalLength = nums1.size() + nums2.size(); if (totalLength % 2 == 1) {//如果是奇数 return getKthElement(nums1, nums2, (totalLength + 1) / 2); } else {//如果是偶数 return (getKthElement(nums1, nums2, totalLength / 2) + getKthElement(nums1, nums2, totalLength / 2 + 1)) / 2.0; } } };
贪心算法(12、整数转罗马数字)
题目描述:
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:
I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
给定一个整数,将其转为罗马数字。输入确保在 1 到 3999 的范围内。
示例 1:
输入: 3
输出: "III"
示例 2:
输入: 4
输出: "IV"
示例 3:
输入: 9
输出: "IX"
示例 4:
输入: 58
输出: "LVIII"
解释: L = 50, V = 5, III = 3.
示例 5:
输入: 1994
输出: "MCMXCIV"
解释: M = 1000, CM = 900, XC = 90, IV = 4.
贪心算法分析
贪心:每次先考虑最大的数,确定了最大的数,减去以后,再考虑后面的数字。例如58,先考虑50--L,再考虑5--V,最后是3---III,所以58为LVIII。
程序
//建立哈希表,对应数字来查找 class Solution { public: string intToRoman(int num) { //建立哈希表 int values[] = {1000,900,500,400,100,90,50,40,10,9,5,4,1}; string romas[] = {"M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"}; //遍历哈希表 string roma; for(int i=0;i<13;i++){ while(num>=values[i]){ num -= values[i]; roma += romas[i]; } } return roma; } };
unordered_map的使用(13、罗马数字转整数)
题目描述
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:
I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内。
示例 1:
输入: "III"
输出: 3
示例 2:
输入: "IV"
输出: 4
示例 3:
输入: "IX"
输出: 9
示例 4:
输入: "LVIII"
输出: 58
解释: L = 50, V= 5, III = 3.
分析
使用map容器,可以将对应的键-值放入其中,再通过比较来取出即可。
程序
class Solution { public: int romanToInt(string s) { //使用map容器比较,需要注意的是,在比较IV时,先比较了I,所以当时已经+1,故使IV对应3,即为3+1=4 unordered_map<string,int> m = {{"I", 1}, {"IV", 3}, {"IX", 8}, {"V", 5}, {"X", 10}, {"XL", 30}, {"XC", 80}, {"L", 50}, {"C", 100}, {"CD", 300}, {"CM", 800}, {"D", 500}, {"M", 1000}}; int ret = m[s.substr(0,1)]; for(int i=1;i<s.size();++i){ string two = s.substr(i-1,2); string one = s.substr(i,1); ret += (m[two]?m[two]:m[one]); } return ret; } };
琐碎
链表的创建
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ ListNode * ret = new ListNode(-1); //返回的头结点
哈希表使用
在查找重复字符时,可以使用哈希表
class Solution { public: int hashmap[130];//建立一张哈希表 int lengthOfLongestSubstring(string s){ int max=0; int n=s.size(); for(int i=0,j=0;j<n;j++){ //哈希表存储s[j]字符,哈希位置+1 hashmap[s[j]]++; //当存储超过一个时,即发现重复的 //左指针右移,移到不与s[j]位置重复的地方,并将沿途中的1变为0 while(hashmap[s[j]]>1){ hashmap[s[i++]]--; } //如果中间的长度>max,则将max重新赋值 if(j-i+1>max){ max=j-i+1; } } return max; } };
二分查找
如果对时间复杂度有log的要求,一般都可以用二分查找
关于数字转换问题
判断是不是数字
- isdigit()函数
- 字符c,(c>='0' && c<='9')
将字符变为数字
- 例如,char c = '4',若变为整数 int a = c-'0'即可
- 使用atoi或者stoi函数,不顾需要注意的是,atoi不会范围检查,超过上限就输出上限,超过下限就输出下限;stoi会做范围检查,超过就报错,而且使用时,格式为
string s = "123"; int a = atoi(s.c_str());
将数字变为字符串
- 使用itoa函数
int a = "123"; char str[25]; itoa(a,str,10);//转换
- 使用to_string函数
int a = 123; string str = to_string(a);
int的范围
INT_MIN(−2^31=-2147483648)~INT_MAX(2^31=2147483647)