scrapy爬虫的学习告一段落,又因为现在在学习数据结构,做题平台是lettcode:https://leetcode-cn.com/
每周都要交一次做题的笔记,所以把相关代码和思路同时放在博客上记录
题目1 ID1
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例:
给定
因为
所以返回
我的解答:
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* twoSum(int* nums, int numsSize, int target, int* returnSize){
int i,j;
int* num;
for(i=0;i<numsSize;i++){
for(j=i+1;j<numsSize;j++){
if(nums[i]+nums[j]==target){
*returnSize=2;
num=(int*)malloc(sizeof(int)*2);
num[0]=i;
num[1]=j;
return num;
}
}
}
return 0;
}
通过嵌套两层for循环的方式暴力求解,返回得到的下标值
题目2 ID7
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
示例
输入: 123
输出: 321
示例 2:
输入: -123
输出: -321
示例
输入: 120
输出: 21
注意:
假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−231, 231 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。
我的解答:
#define MAX 2147483647
#define MIN -2147483648
int reverse(int x){
long num=0;
while(x!=0){
num=num*10+x%10;
x=x/10;
}
return (num>MAX||num<MIN)?0:num;
}
用时0ms,内存消耗6.9MB
最开始num定义的数据类型是int,提交的时候出错了,最后一次的输入将其爆掉了,看了一下题目,意思应该是我们return 结果的那个测试环境,只能储存32位有符号整数,并不是说num是int,所以修改其为long
题目3 ID9
判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
示例
输入: 121
输出: true
示例
输入: -121
输出: false
解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例
输入: 10
输出: false
解释: 从右向左读, 为 01 。因此它不是一个回文数。
进阶:
你能不将整数转为字符串来解决这个问题吗?
我的解答:
bool isPalindrome(int x){
if(x>=0&&x<=9){
return true;
}else if(x<0){
return false;
}else{
long temp,num=0;
temp=x;
while(temp!=0){
num=num*10+temp%10;
temp=temp/10;
}
if(x==num){
return true;
}else{
return false;
}
}
}
用时12ms,内存7.1MB
对于正个位数可知是回文数,负数因为符号的存在而必然不会是回文数,两者都可以直接返回,对于大于9的整数我们可以像上一道题一样先求出它的整数的反转数,然后与其原数相比较,基于回文数的定义,如果是回文数,两数应当相等,如果不是则不等,再返回相应的布尔值即可。需要注意的是测试数字可能很大,所以我们将num,temp都设置成long型。
题目4 ID13
罗马数字包含以下七种字符: 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 的范围内。
示例
输入: "III"
输出: 3
示例
输入: "IV"
输出: 4
示例
输入: "IX"
输出: 9
示例
输入: "LVIII"
输出: 58
解释: L = 50, V= 5, III = 3.
示例
输入: "MCMXCIV"
输出: 1994
解释: M = 1000, CM = 900, XC = 90, IV = 4.
我的解答:
可以看到在题目中给出了六种特殊情况,如果没有特殊情况的话我们只需要遍历传进来的字符串,进行每一位的变化就可以了,有了六种特殊情况之后第一反应就是if...else进行分支
另外,在罗马数字中,小的数字在大的数字的右边,所表示的数等于这些数字相加得到的数,如 Ⅷ=8、Ⅻ=12; 小的数字(限于 Ⅰ、X 和 C)在大的数字的左边,所表示的数等于大数减小数得到的数,如 Ⅳ=4、Ⅸ=9 懂得这个可以写出更好的代码
int romanToInt(char * s){
int i=0;
int sum=0;
for(i=0;s[i];i++){
switch(s[i]){
case 'M':sum+=1000;break;
case 'D':sum+=500;break;
case 'L':sum+=50;break;
case 'V':sum+=5;break;
case 'C':
if(s[i+1]=='D'){
sum+=400;
i++;
}else if(s[i+1]=='M'){
sum+=900;
i++;
}else{
sum+=100;
}
break;
case 'X':
if(s[i+1]=='L'){
sum+=40;
i++;
}else if(s[i+1]=='C'){
sum+=90;
i++;
}else{
sum+=10;
}
break;
case 'I':
if(s[i+1]=='V'){
sum+=4;
i++;
}else if(s[i+1]=='X'){
sum+=9;
i++;
}else{
sum+=1;
}
break;
}
}
return sum;
}
题目5 ID面试题64
求 1+2+...+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
示例 1:
输入: n = 3
输出: 6
示例 2:
输入: n = 9
输出: 45
限制:
1 <= n <= 10000
我的解答:
C/C++的语言特性,使用&&导致逻辑短路
即左侧的表达式为假时整个表达式后续将不再进行评估,算是奇技淫巧吧。
int sumNums(int n){
int sum=n;
n&&(sum+=sumNums(n-1));
return sum;
}
题目6 ID面试题14
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""。
示例
输入: ["flower","flow","flight"]
输出: "fl"
示例
输入: ["dog","racecar","car"]
输出: ""
解释: 输入不存在公共前缀。
说明:
所有输入只包含小写字母 a-z 。
我的解答:
char * longestCommonPrefix(char ** strs, int strsSize){
int i,j;
char* ans=strs[0];
if(strsSize==0){
return "";
}
for(i=1;i<strsSize;i++){
for(j=0;ans[j]!='\0'&&strs[i][j]!='\0';j++){
if(ans[j]!=strs[i][j]){
break;
}
}
ans[j]='\0';
if(ans==NULL){
return "";
}
}
return ans;
}
用ans指向第一个字符串,跟每一个字符串进行比较,当有一位不同的时候退出内层循环,并且将ans截断,如果ans为NULL的话,证明没有一位相同,即无最长公共前缀。否则返回截断得到的ans
题目7 ID20
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例
输入: "()"
输出: true
示例
输入: "()[]{}"
输出: true
示例
输入: "(]"
输出: false
示例
输入: "([)]"
输出: false
示例
输入: "{[]}"
输出: true
我的解答:
bool isValid(char * s){
int i,top=-1;
int length=strlen(s);
char* cha=(char*)malloc(length);
if(s==NULL){
return true;
}
if(length%2!=0){
return false;
}
for(i=0;i<length;i++){
if(s[i]=='('||s[i]=='{'||s[i]=='['){
cha[++top]=s[i];
}else if(top==-1){
return false;
}else if(cha[top]+1==s[i]||cha[top]+2==s[i]){
top--;
}else{
return false;
}
}
return top==-1;
}
左括号先储存在一个数组里面,当遇到右括号的时候弹出,先进后出的结构我们用类似于栈来实现,然后考虑几种情况即可,判断是否匹配的时候使用一个左括号对应的右括号的ASCII码来比较。
题目8 ID21
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
我的解答:
题目给出了指针结构体的格式
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
if(l1==NULL){
return l2;
}
if(l2==NULL){
return l1;
}
if(l1->val<l2->val){
l1->next=mergeTwoLists(l1->next,l2);
return l1;
}else{
l2->next=mergeTwoLists(l1,l2->next);
return l2;
}
}
我们递归调用该函数即可按照大小顺序排列链接链表,需要考虑的问题是返回哪一个链表,我们也可以重新开一个链表,为了方便直接使用l1或l2中的一个,通过比较第一个值来确定返回值,接着递归调用即可
题目9 ID26
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
示例
给定数组
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
你不需要考虑数组中超出新长度后面的元素。
示例
给定
函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
我的解答:
int removeDuplicates(int* nums, int numsSize){
int i,j;
if(nums==NULL||numsSize==0){
return 0;
}
for(i=1,j=1;i<numsSize;i++){
if(nums[i]!=nums[i-1]){
nums[j]=nums[i];
j++;
}
}
return j;
}
双指针法
题目10 ID53
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:
如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
我的解答:
看浙大数据结构的时候有相同的题目,特意来做的,maxnum需要设置大一点才能过审,当thisnum小于0的时候,对于连续子列和的增大就没有帮助了,应当舍弃。更精妙的分治法以后改进。
int maxSubArray(int* nums, int numsSize){
int i,thissum=0,maxsum=-2147483648;
for(i=0;i<numsSize;i++){
thissum+=nums[i];
if(thissum>maxsum){
maxsum=thissum;
}
if(thissum<0){
thissum=0;
}
}
return maxsum;
}
题目11 ID383
给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串ransom能不能由第二个字符串magazines里面的字符构成。如果可以构成,返回 true ;否则返回 false。
(题目说明:为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思。)
注意:
你可以假设两个字符串均只含有小写字母。
canConstruct("a", "b") -> false
canConstruct("aa", "ab") -> false
canConstruct("aa", "aab") -> true
我的解答:
最开始的答案是:
bool canConstruct(char * ransomNote, char * magazine){
int ransomNotenum[26],magazinenum[26];
int i=0;
while(ransomNote[i]){
ransomNotenum[ransomNote[i]-'a']++;
i++;
}
i=0;
while(magazine[i]){
magazinenum[magazine[i]-'a']++;
i++;
}
for(i=0;i<26;i++){
while(ransomNotenum[i]){
ransomNotenum[i]--;
magazinenum[i]--;
}
}
for(i=0;i<26;i++){
if(magazinenum[i]<0){
return false;
}
}
return true;
}
时间超时了,进一步修改
合并为一个数组:
bool canConstruct(char * ransomNote, char * magazine){
int ransomNotenum[26]={0};
int i=0;
while(magazine[i]!='\0'){
ransomNotenum[magazine[i]-'a']++;
i++;
}
i=0;
while(ransomNote[i]!='\0'){
ransomNotenum[ransomNote[i]-'a']--;
i++;
}
for(i=0;i<26;i++){
if(ransomNotenum[i]<0){
return false;
}
}
return true;
}
因为小写字母一共是26个,我们创建一个大小为26的数组,对每个字母出现的次数进行统计,赎金信里面再减去即可得到结果,查看数组中是否有为负数的值,就可知道返回值为true或false,另外应该将数组初始化为0,这里忘记了,找了好久错。
题目12 ID1137
泰波那契序列 Tn 定义如下:
T0 = 0, T1 = 1, T2 = 1, 且在 n >= 0 的条件下 Tn+3 = Tn + Tn+1 + Tn+2
给你整数 n,请返回第 n 个泰波那契数 Tn 的值。
示例 1:
输入:n = 4
输出:4
解释:
T_3 = 0 + 1 + 1 = 2
T_4 = 1 + 1 + 2 = 4
示例 2:
输入:n = 25
输出:1389537
提示:
0 <= n <= 37
答案保证是一个 32 位整数,即 answer <= 2^31 - 1。
我的解答:
很容易会想到斐波那契数列一样的回溯解法:
int tribonacci(int n){
if(n==0){
return 0;
}else if(n==1||n==2){
return 1;
}else{
return tribonacci(n-1)+tribonacci(n-2)+tribonacci(n-3);
}
}
不出意料超时了,在评论区里面学习了一下。
首先可以选择优化回溯算法,题目给出了T(n+3)=T(n)+T(n+1)+T(n+2),故T(n+4)=T(n+1)+T(n+2)+T(n+3),两者相减可得到T(n)=2T(n-1)-T(n+4)
这样return 的时候就少一个回溯了,将代码修改为:
int tribonacci(int n){
switch(n){
case 0:return 0;
case 1:return 1;
case 2:return 1;
case 3:return 2;
case 4:return 4;
default:return 2*tribonacci(n-1)-tribonacci(n-4);
}
}
修改了之后在最后一个输入当n=37的时候还是超出了范围:(
从而想将代码修改为非回溯算法:
int tribonacci(int n){
long reason=0;
long a1=1,a2=1,a3=2;
int i;
if(n==0){
return 0;
}else if(n==1||n==2){
return 1;
}else if(n==3){
return 2;
}else{
for(i=4;i<=n;i++){
reason=a1+a2+a3;
a1=a2;
a2=a3;
a3=reason;
}
return reason;
}
}
修改后通过。