1:两数和
题目:
给定一个数判断是否为回文数。
求解方法:
- hash表查询target-cur是否已经出现。时间复杂度为o(n),空间复杂度为o(n)
- 稳定排序+两指针。时间复杂度为o(nlogn),空间复杂度为o(n);【该方法leetcode无法AC】
注意事项:
-
unordered_map的find是在hash表中查找key,返回iterator
m.find(key)!=m.end()
-
vector res不能先申请两个int大小的空间,后面再push_back之后会返回大小为4的数组!
代码:
class Solution {
public:
/**
* @param numbers: An array of Integer
* @param target: target = numbers[index1] + numbers[index2]
* @return: [index1, index2] (index1 < index2)
*/
vector<int> twoSum(vector<int>& nums, int target) {
return twoSumWayI(nums, target);
}
// way1:hash表;o(n) time,o(n) space
vector<int> twoSumWayI(vector<int> nums, int target) {
unordered_map<int, int> m;//hash表记录元素出现的下标
vector<int> result; //大小为2的结果存储空间,初始化为-1
// 边界
int n = nums.size();
/*if (n == 0 || n == 1) {
return result;
}*/
// 遍历每个元素,同时检查target-当前val是否已经出现过(hash)
for (int i = 0; i < n; i++) {
int checkNum = target - nums[i];
if (m.find(checkNum) != m.end()) {// target-nums[i]出现次数不为0即出现过,已经找到答案
result.push_back(m[checkNum]);
result.push_back(i);
return result;
}
else {
m[nums[i]] = i;//否则记录当前
}
}
return result;
}
// way2:排序+两指针;o(nlogn) time,o(1) space;此题不可取,因为需要返回下标,而排序之后下标已经改变(除非用稳定的归并排序o(nlogn)time,o(1)sapce)
vector<int> twoSumWayII(vector<int> &nums, int target) {
vector<int> result;
// merge sort in asc
mergeSort(nums, 0, nums.size());
// 两相向指针
for (int i = 0, j = nums.size() - 1; i < j; i++) {
if (nums[i] + nums[j] == target) {
result.push_back(i);
result.push_back(j);
return result;
}
else if (nums[i] + nums[j] < target) {
i++;
}
else {
j--;
}
}
return result;
}
// merge排序-devide and conqure;o(nlogn) time,o(1) space
void mergeSort(vector<int> &nums, int left, int right) {
// devide:devide n-problem to n/2 probelm;o(1) time
int mid = left + (right - left) / 2;
mergeSort(nums, left, mid);
mergeSort(nums, mid + 1, right);
// conqure:merge two sorted array;o(n) time
merge(nums, left, mid, right);
}
// merge two sorted array in asc;o(n) time,o(1) space
void merge(vector<int> &nums, int left, int mid, int right) {
int n = nums.size();
if (n == 0 || n == 1) {
return;
}
int lens1 = mid - left + 1, lens2 = right - mid;//size of two subarray
int i = 0, j = 0,k = 0;
while (i < lens1 && j < lens2) {
if (nums[i] <= nums[j]) {
nums[k++] = nums[i++];
}
else {
nums[k++] = nums[j++];
}
}
while (i < lens1) {
nums[k++] = nums[i++];
}
while (j < lens2) {
nums[k++] = nums[j++];
}
}
};
7:整数反转
题目:
给定一个数判断是否为回文数。
求解方法:
- 数字弹出推入 & 溢出检查。判断字符串是否为回文串。时间复杂度 o ( n ) o(n) o(n),空间复杂度为 o ( n ) o(n) o(n)。时间复杂度为 o ( l o g ( x ) ) o(log(x)) o(log(x)),空间复杂度为 o ( 1 ) o(1) o(1)。因为x每次变为原来的10倍,类似于二分 o ( l o g 2 n ) o(log_2^n) o(log2n)
注意事项:
-
x将最右边以为pop出来:
int pop = x %10; x = x/10;
-
将pop加到rev后面之前先判断溢出,因为 r e v = r e v ∗ 10 + p o p rev= rev*10+pop rev=rev∗10+pop会导致溢出。对溢出情况进行分析:
-
正数越界
r e v ∗ 10 + p o p > I N T 32 _ M A X r e v > ( I N T 32 _ M A X − p o p ) 10 rev*10+pop > INT32\_MAX \\ rev > \frac{(INT32\_MAX-pop)}{10} rev∗10+pop>INT32_MAXrev>10(INT32_MAX−pop)
正数越界溢出情况细分为两种:
r e v { 大 于 I N T 32 _ M A X 10 = = I N T 32 _ M A X 10 pop>7 即 p o p ( I N T 3 2 M A X ) 10 rev \begin{cases} 大于\frac{INT32\_MAX}{10} \\ ==\frac{INT32\_MAX}{10} & \text{pop>7}即pop \frac{(INT32_MAX)}{10} \end{cases} rev{大于10INT32_MAX==10INT32_MAXpop>7即pop10(INT32MAX) -
负数越界
r e v ∗ 10 + p o p < I N T 32 _ M I N r e v < ( I N T 32 _ M I N − p o p ) 10 rev*10+pop < INT32\_MIN \\ rev < \frac{(INT32\_MIN-pop)}{10} rev∗10+pop<INT32_MINrev<10(INT32_MIN−pop)
负数越界溢出情况细分为两种:
r e v { 小 于 I N T 32 _ M I N 10 = = I N T 32 _ M I N 10 pop<-8 即 p o p < ( I N T 3 2 M I N ) 10 rev \begin{cases} 小于\frac{INT32\_MIN}{10} \\ ==\frac{INT32\_MIN}{10} & \text{pop<-8}即pop<\frac{(INT32_MIN)}{10} \end{cases} rev{小于10INT32_MIN==10INT32_MINpop<-8即pop<10(INT32MIN) -
越界判断代码
if (rev > INT32_MAX / 10 || (rev == INT32_MAX / 10 && pop > INT32_MAX % 10)) {//正数溢出 return 0; } if (rev < INT32_MIN / 10 || (rev == INT32_MIN / 10 && pop < INT32_MIN % 10)) {//负数溢出 return 0; }
-
代码:
class Solution {
public:
int reverse(int x) {
return reverseWayIPopPush(x);
}
// 数字弹出与推入 & 溢出判断;o(log(x)) time,o(1) space
int reverseWayIPopPush(int x) {
if (x<INT32_MIN || x>INT32_MAX) { // 溢出返回0
return 0;
}
int rev = 0;
while (x != 0) {//当x不等于0时一直循环
// 将x的最右一位数字pop出来
int pop = x % 10;
x = x / 10;
// 溢出判断
if (rev > INT32_MAX / 10 || (rev == INT32_MAX / 10 && pop > INT32_MAX % 10)) {//正数溢出
return 0;
}
if (rev < INT32_MIN / 10 || (rev == INT32_MIN / 10 && pop < INT32_MIN % 10)) {//负数溢出
return 0;
}
// 将pop出来的数push到rev的后面
rev = rev * 10 + pop;
}
// 返回结果
return rev;
}
};
344:翻转字符串
题目:
给定一个数判断是否为回文数。
求解方法:
- 两相向指针。时间复杂度 o ( n ) o(n) o(n),空间复杂度为 o ( 1 ) o(1) o(1)。
- 库函数reverse。
代码:
class Solution{
public:
// 要求原地反转字符串
void reverseString(vector<char>& s) {
reverseStringWayII(s);
}
// 方法1:两相向指针;o(n) time,o(1) space
void reverseStringWayI(vector<char>& s) {
int n = s.size();//字符串长度
for (int i = 0, j = n - 1; i <= j;) {
swap(s[i++], s[j--]);
}
}
//方法2:利用库函数;比方法1快
void reverseStringWayII(vector<char>& s) {
reverse(s.begin(), s.end());
}
};
9:回文数
题目:
给定一个数判断是否为回文数。
求解方法:
- 数字转字符串。判断字符串是否为回文串。时间复杂度 o ( n ) o(n) o(n),空间复杂度为 o ( n ) o(n) o(n)。
- 反转一半数字。反转数字后半部分,判断是否和前半部分相等或者是否为前半部分的10倍。时间复杂度为 o ( l o g ( x ) ) o(log(x)) o(log(x)),空间复杂度为 o ( 1 ) o(1) o(1)
注意事项:
- 越界情况:负数、非0且为10的倍数
- 判断已经翻转了一半:reverseNum>=x时候(长度大于x时肯定大于x)
- 最后判断x和reverseNum是否相等(偶数位)或者reverseNum是x的10倍(奇数位)
注:reverseNum是数字后半部分reverse之后的结果
代码:
class Solution {
public:
bool isPalindrome(int x) {
return isPalindromeWayII(x);
}
// way1:将数字转化为字符串;o(n) time,o(n) space
bool isPalindromeWayI(int x) {
string str = to_string(x);
// 判断字符串是否为回文
for (int i = 0, j = str.size() - 1; i <= j;) {
if (str[i] == str[j]) {
i++;
j--;
}
else {
return false;
}
}
return true;
}
// way2:不将数字转化为字符串,而是反转字符串本身,若反转前后相同则为palidrom number;
// o(log(x)) time,o(1) space
// 但数字反转容易导致溢出,为了避免数字反转可能导致的溢出问题。考虑只反转数字的一半,如果该数字是回文,其后半部分反转后应该与原始数字的前半部分相同。
//例如,输入 1221,我们可以将数字 “1221” 的后半部分从 “21” 反转为 “12”,并将其与前半部分 “12” 进行比较,因为二者相同,我们得知数字 1221 是回文
/*算法
(1)首先,我们应该处理一些临界情况。
所有负数都不可能是回文,例如: - 123 不是回文,因为 - 不等于 3。所以我们可以对所有负数返回 false。
当数字末尾为0时,要求第一位为0。所以当数字是10的倍数但不是0的时候就返回false。
(2)现在,让我们来考虑如何反转后半部分的数字。
对于数字 1221,如果执行 1221 % 10,我们将得到最后一位数字 1,要得到倒数第二位数字,我们可以先通过除以 10 把最后一位数字从 1221 中移除,1221 / 10 = 122,再求出上一步结果除以 10 的余数,122 % 10 = 2,就可以得到倒数第二位数字。如果我们把最后一位数字乘以 10,再加上倒数第二位数字,1 * 10 + 2 = 12,就得到了我们想要的反转后的数字。如果继续这个过程,我们将得到更多位数的反转数字。
(3)现在的问题是,我们如何知道反转数字的位数已经达到原始数字位数的一半?
我们将原始数字除以 10,然后给反转后的数字乘上 10,所以,当原始数字小于反转后的数字时,就意味着我们已经处理了一半位数的数字。
*/
bool isPalindromeWayII(int x) {
if (x < 0 || (x % 10 == 0 && x != 0)) {
return false;
}
int reverseNumber = 0;
while (x > reverseNumber) { // 当弹出最右侧数字push到reverseNumber后,如果reverseNumber>x则表示已经处理了一半了
int pop = x % 10;
x = x / 10;
reverseNumber = reverseNumber * 10 + pop;
}
// 判断前半和后半翻转后的结果是否相同
// 分奇数和偶数位,偶数位要求x=reverseNumber,奇数位要求x = reverseNum /10(reverse/10相当于去除中位数)
return x == reverseNumber || x == reverseNumber / 10;
}
};
罗马数字转整数
题目:
罗马字符共七个,每个均代表一定的数字,将罗马字符串转化为数字。
思路:
- hash表存储罗马字符和数字的映射关系。比较当前字符和下一个字符所对应的数字大小决定加还是减当前字符。
代码:
class Solution {
public:
// 首先建立一个HashMap来映射符号和值,然后对字符串从左到右来,如果当前字符代表的值不小于其右边,就加上该值;否则就减去该值。以此类推到最左边的数,最终得到的结果即是答案
int romanToInt(string s) {
int n = s.size();
int sum = 0;
// 构建Hashmap
unordered_map<char, int> m;
m['I'] = 1;
m['V'] = 5;
m['X'] = 10;
m['L'] = 50;
m['C'] = 100;
m['D'] = 500;
m['M'] = 1000;
for (int i = 0; i < n; i++) {
if (i == n - 1) {//最后一位
sum += m[s[i]];
}
else {
int firstValue = m[s[i]];
int nextValue = m[s[i + 1]];
if (firstValue >= nextValue) {
sum += firstValue;
}
else {
sum -= firstValue;
}
}
}
return sum;
}
};
14:最长公共前缀
题目:
给定字符串数组,找到数组中所有字符串的公共前缀的长度。
思路:
- 找到数组中ASCII码最大s1和最小的字符串s2;只需要得到s1和s2的最大公前缀即可。
注意事项:
- 利用max_element和min_element函数找到容器中最大和最小元素所对应的迭代器。
- 利用mismatch找到两个字符串第一个不匹配的位置的(2个)迭代器。
- auto类型的s1,要使用s1->begin()而不是s1.begin()
代码:
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
if (strs.size() == 0) {
return "";
}
// 利用max_element和min_element得到容器的最大值和最小值
auto s1 = max_element(strs.begin(), strs.end());
auto s2 = min_element(strs.begin(), strs.end());
// equal和mismatch算法的功能是比较容器中的两个区间内的元素。
//这两个算法各有3个参数first1,last1和first2.如果对于区间[first1,last1)内所有的first1+i,first1+i和first2所在位置处的元素都相等,则equal算法返回真,否则返回假。
//mismatch算法的返回值是由两个迭代器first1+i和first2+i组成的一个pair,表示第1对不相等的元素的位置。如果没有找到不相等的元素,则返回last1和first2+(last1-first1)。因此,语句
auto pair = mismatch(s1->begin(), s1->end(), s2->begin());
string result = string(s1->begin(), pair.first);
return result;
}
};
20:有效的括号
题目:
检查字符串中的括号是否配对。
思路:
- 栈。当为左括号时入展,右括号时检查,若栈空或者栈顶元素与当前元素不配对则返回false,否则将栈顶元素弹出。
- 时间复杂度为 o ( n ) o(n) o(n),空间复杂度为 o ( n ) o(n) o(n)
代码
// 有效的括号
class IsValidA {
public:
bool isValid(string s) {
int n = s.size();
if (n == 0) {//空字符是有效的括号
return true;
}
stack<int> stacks;
for (int i = 0; i < n; i++) {
if (s[i] == '[' || s[i] == '{' || s[i] == '(') {
stacks.push(s[i]);
}
else {
if (stacks.empty()) return false;
if (s[i] == '}' && stacks.top() != '{') return false;
if (s[i] == ')' && stacks.top() != '(') return false;
if (s[i] == ']' && stacks.top() != '[') return false;
stacks.pop();
}
}
return stacks.empty();
}
};
21:合并两个有序链表
题目:
将两个有序链表合并为一个有序链表。
思路:
- 利用dummy node.因为两个链表不确定哪一个的node作为头节点,当头节点不确定或者改变时候使用dummy node。
- 时间复杂度 o ( n + m ) o(n+m) o(n+m),空间复杂度 o ( n + m ) o(n+m) o(n+m)
代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
// 因为不确定哪一个是合并后的头节点,所以建立dummy Node
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode* dummy = new ListNode(0);
ListNode* head = dummy;
while (l1 != NULL && l2 != NULL) {
if (l1->val <= l2->val) {
head->next = l1;
l1 = l1->next;
}
else {
head->next = l2;
l2 = l2->next;
}
head = head->next;
}
if (l1 != NULL) {
head->next = l1;
}
if (l2 != NULL) {
head->next = l2;
}
return dummy->next;
}
};
26:删除排序数组中的重复项
题目:
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
思路:
- 两同向指针。最初均指向0, j j j指向不重复元素的最后一位, i i i一直往前走;当 n u m s [ j ] nums[j] nums[j]不等于 n u m s [ i ] nums[i] nums[i]时候,把 n u m s [ i ] nums[i] nums[i]的值赋给 n u m s [ + + j ] nums[++j] nums[++j],即赋值给 j j j的下一位,同时 j j j向右移动;最后返回j+1。【因为让返回数组长度且j指向符合条件的最后一位】
- 时间复杂度 o ( n ) o(n) o(n),空间复杂度 o ( 1 ) o(1) o(1)。
代码:
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int n = nums.size();
if (n == 0) {
return 0;
}
int i = 0, j = 0;
for (; i < n; i++) {
if (nums[i] != nums[j]) {
nums[++j] = nums[i];
}
}
return j + 1;
}
};
27:移除元素
题目:
给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
思路:
- 两同向指针。最初均指向0, j j j指向符合条件区域的下一位, i i i一直往前走;当 n u m s [ i ] nums[i] nums[i]不等于 v a l val val时候,把 n u m s [ i ] nums[i] nums[i]的值赋给 n u m s [ j + + ] nums[j++] nums[j++],即赋值给 j j j的同时将 j j j向右移动;最后返回j。【因为让返回数组长度并且j指向符合条件区域的下一位】
- 时间复杂度 o ( n ) o(n) o(n),空间复杂度 o ( 1 ) o(1) o(1)。
代码:
//27:原地移除数组中值为val的数,返回数组长度
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int j = 0;
for (int i = 0; i < nums.size(); i++) {
if (nums[i] != val) {
nums[j++] = nums[i];
}
}
return j;
}
};
28:实现strStr()
题目:
实现 strStr() 函数。
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。
思路:
- 暴力求解。时间复杂度为
o
(
n
∗
m
)
o(n*m)
o(n∗m),空间复杂度为
o
(
1
)
o(1)
o(1)。利用两只指针,初始
i
i
i和
j
j
j均指向0,当
i
i
i和
j
j
j中任意一个没有到达最后时遍历。分为2种情况,(1)当前字符对应相等,则
i
i
i和
j
j
j同时向右移动;(2)若不想等,则将子字符串向右移动一位,即
i
i
i进行回溯,
j
j
j归0。以代码形式则为:
i = i-(j-1); j=0;
- 利用库函数find。
- 利用kmp算法。
代码:
class Solution {
public:
int strStr(string haystack, string needle) {
return strStrWayI(haystack, needle);
}
// way1:暴力求解o(n*m)--不能是needle.size()==0而是needl.empty()时候返回0
int strStrWayI(string haystack, string needle) {
if (needle.empty()) {// 当子串为空的时候返回0
return 0;
}
int lens1 = haystack.size(), lens2 = needle.size();
int i = 0, j = 0;
while (haystack[i] != '\0' && needle[j] != '\0') {
if (haystack[i] == needle[j]) {
i++;
j++;
}
else {
i = i - j + 1;
j = 0;
}
}
if (needle[j] == '\0') {
return i - j;
}
else {
return -1;
}
}
//way2:利用库函数find
int strStrWayII(string haystack, string needle) {
if (needle.size() == 0) {
return 0;
}
int pos = haystack.find(needle);
return pos;
}
// way3:KMP
// way4:BM
};
35:搜索插入位置
题目:
思路:
- 暴力扫描。
- 二分法。
代码:
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
return searchInsertWayII(nums,target);
}
// way1:扫描;时间复杂度为o(n),空间复杂度为o(1)
int searchInsertWayI(vector<int>& nums, int target) {
int n = nums.size();
if (n == 0) {
return 0;
}
// 暴力搜索即可
for (int i = 0; i < n; i++) {
if (nums[i] >= target) {
return i;
}
}
return n;
}
// way2:二分;时间复杂度为o(logn),空间复杂度为o(1)
int searchInsertWayII(vector<int>& nums, int target) {
// 找到第一个大于等于
int n = nums.size();
if (n == 0) {
return 0;
}
int left = 0, right = n - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
}
else if (nums[mid] > target) {
right = mid - 1;
}
else {
left = mid + 1;
}
}
// 左边第一个大于等于
if (nums[left] >= target) {
return left;
}else{
return left+1;
}
}
};
- 38:Count and Say