1.两数之和——基于数组的散列
给定一个整数组nums和一个目标值target,请你在该数组中找出和目标之中那两个整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
示例:
给定 nums = [2,7,11,15], target =9
因为 nums[0]+nums[1] = 2+7=9
所 以返回[0,1]
暴力解法
遍历法
/*
*遍历法
*/
int *twoSum(int *nums, int numsSize, int target, int *returnSize)
{
int *result = (int *)malloc(sizeof(int) * 2);
for (int i = 0; i < numsSize - 1; i++)
{
for (int j = i + 1; j < numsSize; j++)
{
if (nums[i] + nums[j] == target)
{
result[0] = i;
result[1] = j;
*returnSize = 2;
return result;
}
}
}
return result;
}
/*
*第二层查找目标与第一层循环对应值的差是否存在
*/
int* twoSum(int* nums, int numsSize, int target, int* returnSize){
int *result = (int *)malloc(sizeof(int)*2);
int def = 0;
for(int i=0;i<numsSize-1;i++)
{
def = target - nums[i] ;
for(int j = i+1 ; j<numsSize-1;j++)
{
if(def==nums[j])
{
result[0] = i;
result[1] = j;
*returnSize =2 ;
return result;
}
}
}
return result;
}
哈希散列
直接使用哈希散列,将nums中的元素值当下标,nums的下标当值储存在数组中:
1.初始化hash[2000],初始值设为-1;
2.遍历数组,查看target-nums[i]为下标的hash数组元素值(hash[ target -nums[i]] 是否为-1;
3.若为-1,将下标i存放在hash数组的nums[i]位置上,hash[ nums[i] ] = i ;
4.若不为-1,即存在相加为target的元素,两个元素的下标为hash[ target-nums[i] ] ,i ;
注意: 测试用例中存在负数,在散列时会访问越界,故使用求 余法,将负数散列到数组尾部 : hash[(nums[i] + MAX_SIZE)
% MAX_SIZE] = i; 查找时也要如此; 就是负数放到后面
#define MAX_SIZE 2048
int *twoSum(int *nums, int numsSize, int target, int *returnSize)
{
int i, hash[MAX_SIZE], *res = (int *)malloc(sizeof(int) * 2);
memset(hash, -1, sizeof(hash));//meset()用于设置某一段内存内容
for (i = 0; i < numsSize; i++)
{
if (hash[(target - nums[i] + MAX_SIZE) % MAX_SIZE] != -1)
{
res[0] = hash[(target - nums[i] + MAX_SIZE) % MAX_SIZE];
res[1] = i;
*returnSize = 2;
return res;
}
hash[(nums[i] + MAX_SIZE) % MAX_SIZE] = i; //防止负数下标越界,循环散列
}
free(hash);
*returnSize = 0;
return res;
}
2.两数相加
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
虚拟节点
1.设置虚拟头结点:为防止内存泄露,虚拟头结点需删除
struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2){
int c=0;
struct ListNode *head=(struct ListNode *)malloc(sizeof(struct ListNode)),*cur=head,*del=head;
//head虚拟头结点地址,cur当前节点地址,del用于删除虚拟头结点
while(l1!=NULL||l2!=NULL||c)
{
cur->next=(struct ListNode *)malloc(sizeof(struct ListNode));
cur=cur->next;
l1=l1!=NULL?(c+=l1->val,l1->next):l1;
l2=l2!=NULL?(c+=l2->val,l2->next):l2;
cur->val=c%10;
c=c/10;
}
cur->next=NULL;
head=head->next;
free(del);
return head;
}
2.不设置虚拟头结点:
struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2){
int c=0;
//if(l1==NULL&&l2==NULL)return NULL;
struct ListNode *head=(struct ListNode *)malloc(sizeof(struct ListNode)),*cur=head;
while(cur!=NULL)//条件也可以写l1!=NULL||l2!=NULL||c
{
l1 = (l1 != NULL) ? (c += l1->val, l1->next) : l1;
l2 = (l2 != NULL) ? (c += l2->val, l2->next) : l2;
cur->val = c % 10;
c /= 10;
cur->next = (l1!=NULL || l2!=NULL || c!=0)?(struct ListNode *)malloc(sizeof(struct ListNode)) : NULL;
cur = cur->next;
}
return head;
}
递归
int c=0;
struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2){
if(l1==NULL && l2==NULL && c==0)return NULL;
l1 = l1!=NULL ? (c += l1->val, l1->next) : l1;
l2 = l2!=NULL ? (c += l2->val, l2->next) : l2;
struct ListNode *cur = (struct ListNode *)malloc(sizeof(struct ListNode));
cur->val = c%10;
c /= 10;
cur->next = addTwoNumbers(l1,l2);
return cur;
}
核心代码:
l1=l1!=NULL?(c+=l1->val,l1->next):(c+=0,l1);
l2=l2!=NULL?(c+=l2->val,l2->next):(c+=0,l2);
c即进位,初始值0
上一位的进位(c值)加上两个链表相同位的数值,(c的个位)为当前位的结果,(c的十位)为当前位的进位
利用三目运算符:
l1不为空,说明当前位还有值,则加l1当前位的值,l1地址指向下一位
l1为空,说明l1加完了,就给进位加0(不变),l1始终不变指向空
l2同理,直到l1,l2都为空,c为0停止
蓝桥杯——连号区间数
题目描述:
小明这些天一直在思考这样一个奇怪而有趣的问题:
在1~N的某个全排列中有多少个连号区间呢?这里所说的连号区间的定义是:
如果区间[L, R] 里的所有元素(即此排列的第L个到第R个元素)递增排序后能得到一个长度为R-L+1的“连续”数列,
则称这个区间连号区间。
(注意此题解题关键在于数列从小到大排序最大值减最小值就是区间长度;是直接将数组操作,不是进行递增排序后在找的)
当N很小的时候,小明可以很快地算出答案,但是当N变大的时候,问题就不是那么简单了,现在小明需要你的帮助。
输入格式:
第一行是一个正整数N (1 <= N <= 50000), 表示全排列的规模。
第二行是N个不同的数字Pi(1 <= Pi <= N), 表示这N个数字的某一全排列。
输出格式:
输出一个整数,表示不同连号区间的数目。
示例:
用户输入:
4
3 2 4 1
程序应输出:
7
用户输入:
5
3 4 2 5 1
程序应输出:
9
思考
1.什么是连续?——根据样例 连续的自然数
2.怎么核验?——思考十分钟发现每一个连续数列按照从小到大排列的最大值与最小值的差等于长度减一。
3.数列做差与位置的关系?——采用方法使数组逐渐增大,那样在双层循环最小的一直在[L,R]的位置上,不一定要移动,但是最小的一定在最前面第二条才能用得上!!!!!
代码
#define _CRT_SECURE_NO_WARNINGS //使用高版本vs解决scanf不安全的问题
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int main()
{
int i, j, n, L, R, sum = 0, a[99999];
scanf("%d", &n);
for (i = 0; i < n; i++)
scanf("%d", &a[i]);
for (i = 0; i < n; i++)
{
sum++;
L = a[i];
R = a[i];
for (j = i + 1; j < n; j++)
{
if (a[j] > R)
R = a[j];
if (a[j] < L)
L = a[j];
if (R - L == j - i)
sum++;
}
}
printf("%d\n", sum);
return 0;
}
算法效率
724. 寻找数组的中心索引
给定一个整数类型的数组 nums,请编写一个能够返回数组 “中心索引” 的方法。
我们是这样定义数组 中心索引 的:数组中心索引的左侧所有元素相加的和等于右侧所有元素相加的和。
如果数组不存在中心索引,那么我们应该返回 -1。如果数组有多个中心索引,那么我们应该返回最靠近左边的那一个。
示例 1:
输入:
nums = [1, 7, 3, 6, 5, 6]
输出:3
解释:
索引 3 (nums[3] = 6) 的左侧数之和 (1 + 7 + 3 = 11),与右侧数之和 (5 + 6 = 11) 相等。
同时, 3 也是第一个符合要求的中心索引。
示例 2:
输入:
nums = [1, 2, 3]
输出:-1
解释:
数组中不存在满足此条件的中心索引。
说明:
nums 的长度范围为 [0, 10000]。
任何一个 nums[i] 将会是一个范围在 [-1000, 1000]的整数。
思路:left_sum + nums[i] + right_sum = sum
int pivotIndex(int* nums, int numsSize)
{
int left_sum = 0;
int sum = 0;
if (numsSize == 1) {
return 0;
}
for (int i = 0; i < numsSize; i++) {
sum += nums[i];
}
for (int i = 0; i < numsSize; i++) {
if (left_sum == sum - left_sum - nums[i]) {
return i;
}
left_sum += nums[i];
}
return -1;
}
蓝桥杯——Excel地址
Excel单元格的地址表示很有趣,它使用字母来表示列号:A表示第1列,Z表示第26列,AB表示第28列,
本题目既是要求对输入的数字, 输出其对应的Excel地址表示方式。
示例1:
input:26 output:Z
示例2:
input:2054 output:BZZ
代码
#include <stdio.h>
#define CH 'A' // 宏定义:用CH表示大写字母A
char p[10];
int main() {
int m;
scanf("%d", &m); // 输入:列数
int i = 0;
while (m>0) {
p[i++] = (m - 1) % 26 + CH; // 计算最后那个字母
m -= (m - 1) % 26 + 1; // 把它的前一列数字去掉之后所剩下的
m /= 26; // 前一列数字计算完之后,剩下的肯定能被 26 整除
}
p[i] = '\0';
// 倒序输出字母
for (i = 9; i >= 0; i--) {
if (p[i] != '\0') printf("%c", p[i]);
}
return 0;
}
代码解析
p[i++] = (m - 1) % 26 + CH; // 计算最后那个字母
m -= (m - 1) % 26 + 1; // 把它的前一列数字去掉之后所剩下的
m /= 26; // 前一列数字计算完之后,剩下的肯定能被 26 整除
我个人的理解是进制问题,十进制数字转换为特殊26进制数字(无0),纯粹的除法不能去掉特殊进制的特殊进位,所以采用求与做差!
面试题 01.07. 旋转矩阵
给你一幅由 N × N 矩阵表示的图像,其中每个像素的大小为 4 字节。请你设计一种算法,将图像旋转 90 度。
不占用额外内存空间能否做到?
示例 1:
给定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],
原地旋转输入矩阵,使其变为:
[
[7,4,1],
[8,5,2],
[9,6,3]
]
示例 2:
给定 matrix =
[
[ 5, 1, 9,11],
[ 2, 4, 8,10],
[13, 3, 6, 7],
[15,14,12,16]
],
原地旋转输入矩阵,使其变为:
[
[15,13, 2, 5],
[14, 3, 4, 1],
[12, 6, 8, 9],
[16, 7,10,11]
]
思路:
先转置再逆序
转置: 对角线为轴两边互换
逆序:更改顺序
零矩阵
编写一种算法,若M × N矩阵中某个元素为0,则将其所在的行与列清零。
示例 1:
输入:
[
[1,1,1],
[1,0,1],
[1,1,1]
]
输出:
[
[1,0,1],
[0,0,0],
[1,0,1]
]
示例 2:
输入:
[
[0,1,2,0],
[3,4,5,2],
[1,3,1,5]
]
输出:
[
[0,0,0,0],
[0,4,5,0],
[0,3,1,0]
]
代码
void setZeroes(int** matrix, int matrixSize, int* matrixColSize){
int cnt[matrixSize][*matrixColSize]; //用于记录0的位置
int i,j,k;
for(i=0;i<matrixSize;i++){ //记录0的位置
for(j=0;j<*matrixColSize;j++){
if(matrix[i][j]==0){
cnt[i][j]=1;
}
}
}
for(i=0;i<matrixSize;i++){ //置0
for(j=0;j<*matrixColSize;j++){
if(cnt[i][j]==1){
for(k=0;k<*matrixColSize;k++){ //置行为0
matrix[i][k]=0;
}
for(k=0;k<matrixSize;k++){ //置列为0
matrix[k][j]=0;
}
}
}
}
}
3.3. 无重复字符的最长子串
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
int lengthOfLongestSubstring(char * s){
int start=0,end=0,maxlen=0;
char map[256] = {0};
map[(int)* (s+start)]=1;
while(*(s+end)!=0)
{
maxlen = maxlen>(end-start+1)?maxlen:(end-start+1);
++end;
while(0!=map[(int)*(s+end)])
{
map[(int)*(s+start)]=0;
++start;
}
map[(int)*(s+end)] =1;
}
return maxlen;
}
窗口
5. 最长回文子串
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: "babad"
输出: "bab"
注意: “aba” 也是一个有效答案。
示例 2:
输入: "cbbd"
输出: "bb"
代码
char * longestPalindrome(char * s){
int N = strlen(s), start = 0, len = 0; // N 字符串长度, start 子串起始位置, len 子串长度
for (int i = 0; i < N; i++) { // 奇数长度的回文子串
int left = i - 1, right = i + 1;
while (left >= 0 && right < N && s[left] == s[right]){
left--; right++; // 以 i 为中心,向两侧延伸,直到不再满足回文
} // left+1 ~ right-1 则为以i为中心的最大回文子串
if (right - left - 1 > len) { // 若更长,则保存该子串信息
start = left + 1;
len = right - left - 1;
}
}
for (int i = 0; i < N; i++) { // 偶数长度的回文子串
int left = i, right = i + 1; // 以 i+0.5 为中心,向两侧延伸
while (left >=0 && right < N && s[left] == s[right]) {
left--, right++;
}
if (right - left - 1 > len) {
start = left + 1;
len = right - left - 1;
}
}
s[start + len] = '\0'; // 原地修改返回
return s + start;
}
核心代码
s[left]==s[right]
奇数长和偶数长下标奇偶不同!