用到的数据结构:链表实现的栈
参考:C语言—堆栈(链表实现) - changfan - 博客园
舍友看到我这一大堆栈的实现惊了,说你机试的时候真的有这么多时间写C吗?于是去看了看C++的写法——短时间里不能很熟练运用java,就先上C++吧。下面是几个需要学习的Part:
关于map的使用
在C里面,我是对着ascii码表看的,或者直接比较,而在C里面可以用Map做一个一对一的hash映射。参考:
C++中map的使用_u011555996的博客-CSDN博客_c++ map
map与unordered_map的区别
.empty()
判断容器是否为空。
需要注意的是,在stack.top()顶端元素时,stack不可以为空。
.size()
在获取字符串长度时,size()函数与length()函数作用相同。 除此之外,size()函数还可以获取vector类型的长度。
我自己写的方法:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
struct ListNode *p1 = l1;
struct ListNode *q1 = l2;
struct ListNode *cur = (struct ListNode*)malloc(sizeof(struct ListNode));
struct ListNode *result = cur;
int val1 = 0;
int val2 = 0;
while((p1 != NULL)||(q1 != NULL))
{
val1 = (p1 != NULL)?p1->val:32767;
val2 = (q1 != NULL)?q1->val:32767;
cur -> next = (struct ListNode*)malloc(sizeof(struct ListNode));
cur = cur->next;
if(val1 <= val2)
{
cur->val = val1;
p1 = p1->next;
}
else
{
cur->val = val2;
q1 = q1->next;
}
}
while(p1 != NULL)
{
cur -> next = (struct ListNode*)malloc(sizeof(struct ListNode));
cur = cur->next;
cur->val = p1->val;
p1 = p1->next;
}
while(q1 != NULL)
{
cur -> next = (struct ListNode*)malloc(sizeof(struct ListNode));
cur = cur->next;
cur->val = q1->val;
q1 = q1->next;
}
cur->next = NULL;
return result->next;
}
看到一种递归的写法:
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
if (!l1) //如果l1为空
return l2;
if (!l2) //如果l2为空
return l1;
if (l1->val < l2->val){
l1->next = mergeTwoLists(l1->next, l2);
return l1;
}
else{
l2->next = mergeTwoLists(l1, l2->next);
return l2;
}
}
两种方法的运行时间和内存消耗差不多,递归的不好想但是写起来更简洁。
在这里学习一下C++的数组。
C++ vector& nums 用法一_年年のBlog-CSDN博客_vector& nums
线性搜索:
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int i;
for(i=0 ; i<nums.size() ; i++)
{
if(nums[i] >= target)
return i;
}
return i;
}
char * countAndSay(int n){
if(n == 1)
return "1";
char *str = countAndSay(n-1);
char *nstr = malloc(5000); //n<30 最大需要5000长度的字符串
int i = 0,len = strlen(str);
int count = 1,j = 0;
char key,num;
while(i < len)
{
key = str[i]; //下面统计key出现的次数
i++;
while(str[i] == key && i<len)
{
i++;
count++;
}
num = count+48; //共出现了count次,转换为字符,'0'的ascii码是48
nstr[2*j] = num;
nstr[2*j+1] = key;
count = 1;
j++;
}
nstr[2*j] = '\0'; //没有初始化过,堵上,不然会乱码
return nstr;
}
用C++写的话,是一样的逻辑,但是就慢了很多……没有学过C++的程序优化,所以不太清楚是哪里慢了。不过拼接字符串是很爽的事情。
class Solution {
public:
string countAndSay(int n) {
if(n == 1)
return "1";
string str = countAndSay(n-1);
char key;
int count = 1;
int i = 0,len = str.length();
string nstr = "";
while(i<len)
{
key = str[i];
i++;
while(i<len && str[i] == key)
{
count++;
i++;
}
nstr += to_string(count) + key;
count = 1;
}
return nstr;
}
};
这一题有大神写了很棒的题解:题解
法1:动态规划
用dp[i]表示以nums[i]结尾的最大子序和。
dp[0] = nums[0];
dp[i] = max{ dp[i-1]+nums[i] , nums[i] };
当然也可以只记录最大dp值,这样就可以不用存整个dp数组,空间上更小。
这里又用到了一个很好用的库.max()
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int numsize = nums.size();
int dp = nums[0];
int result = dp;
int i;
for(i=1 ; i<numsize ; i++)
{
dp = max(dp+nums[i],nums[i]);
result = max(dp,result);
}
return result;
}
};
法2:贪心法
贪心算法不一定能找到最优解。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int numsize = nums.size();
int sum = 0;
int result = INT_MIN;
int i;
for(i=0 ; i<numsize ; i++)
{
sum += nums[i];
result = max(result,sum);
if(sum < 0)
sum = 0;
}
return result;
}
};
别的方法就有些复杂,对这道题不是很适用了。
一开始是这样写的:
class Solution {
public:
vector<int> plusOne(vector<int>& digits) {
int len = digits.size();
int i,carry = 0;
vector<int> res;
for(i=len-1 ; i>=0 ; i--)
{
if(i == len-1)
digits[i]++;
digits[i] += carry;
carry = (digits[i])/10;
res.insert(res.begin(),digits[i]%10);
}
if(carry)
res.insert(res.begin(),1);
return res;
}
};
然后我被自己的内存消耗暴击了……后面没有单独声明数组res,差别不大。
这里注意一个写法:在数组头部插入res.insert(res.begin(),num);
然后发现大家在进位这件事上做文章。从后向前遍历,置所有的9为0,将第一个不为9的数+1.
特殊情况下,9999……99999,则需要在最前面补1.
class Solution {
public:
vector<int> plusOne(vector<int>& digits) {
int len = digits.size();
int i;
for(i=len-1 ; i>=0 ; i--)
{
if(digits[i] == 9)
digits[i] = 0;
else
{
digits[i]++;
return digits;
}
}
if(i < 0)
digits.insert(digits.begin(),1);
return digits;
}
};
然后我仍旧被内存消耗暴击,甚至比之前还多……不知道为什么别人这样写就是双百。
二分法啦
class Solution {
public:
int mySqrt(int x) {
if(x == 1 || x == 0)
return x;
int min = 0, max = x;
int res,last = 0;
while(true)
{
res = (min+max)/2;
if(last == res)
return res-1;
if(x/res < res)
max = res;
else
min = res+1;
last = res;
}
}
};
需要注意的是,为了避免乘法溢出,我们使用除法来判断大小。
k = (min+max)/2;
如果k大了,到(min,k)里去找
如果k小了,到(k+1,max)里去找
结果偏大,-1.
这真的不是在考我小学奥数?
首先要理解这个题目,得到一个动态规划的式子:
f(x) = f(x-1)+f(x-2);
即,爬x阶楼梯的方法数 = 爬x-1阶的方法数+爬x-2阶的方法数
因为倒着想,爬到x阶前,你可以爬1阶,也可以爬2阶。如果是爬1阶,那么你已经爬了x-1阶;如果是爬2阶,那么你已经爬了x-2阶。
写到这里我已经不认识爬字了。
好了,理解了这个之后,我们就可以得到一个斐波那契数列:1,2,3,5,8,13……
所以,这就是求斐波那契数列的一个题,understand?
而在理解之前,我一直尝试用排列组合求解(跪)
解斐波那契数列有两种比较容易想到的方法:1.递归;2.滑动窗口
在上C语言课的时候,我们都在这里学习了递归的思想,然而实际上用递归的话,函数套函数,卷卷卷卷卷,到n = 44就会超时。所以标答给的是滑动窗口的解法。下面是我自己根据滑动窗口写的答案,双百,时间复杂度是O(n).
class Solution {
public:
int climbStairs(int n) {
if(n == 1)
return 1;
else if(n == 2)
return 2;
int a = 1, b = 2, c;
int i;
for(i=3;i<n+1;i++)
{
switch(i%3){
case 0:
c = a+b;
break;
case 1:
a = c+b;
break;
case 2:
b = a+c;
break;
}
}
i = max(a,b);
return max(i,c);
}
};
当然斐波那契数列由来已久,大家闲得发慌,给出了几种时间复杂度O(logn)的解法。至此,这道题已经从小学奥数题变成了考研数学题,需要用到一系列线代知识和高数知识。本来我买考研数学辅导书回来只是想回忆一下这些知识,还被一众小伙伴嘲笑浪费钱,但是事实证明,大学里学的数学真的很重要!!!
官方解答