leetcode 题解
两数之和
方法1:暴力破解
时间复杂度:O(N2)
空间复杂度:O(1)
go代码
package mainimport "fmt"
func twoSum(nums []int, target int) []int {
// 暴力破解
for i := 0; i < len(nums); i++ {
if nums[i] >= target {
break
}
for j := i + 1; j < len(nums); j++ {
if nums[i]+nums[j] == target {
return []int{i, j}
}
}
}
return nil
}
func main() {
a := []int{0, 7, 11, 0}
fmt.Println(twoSum(a, 0))
}
方法2:Hash表
时间复杂度:O(N)
空间复杂度:O(N) --空间换时间
思路:创建一个哈希表,对于每一个 x
,我们首先查询哈希表中是否存在 target - x
,然后将 x
插入到哈希表中,即可保证不会让 x
和自己匹配
go代码
package mainimport "fmt"
func twoSum(nums []int, target int) []int {
// hash表
hashtable := map[int]int{}
for i := 0; i < len(nums); i++ {
if p, ok := hashtable[target-nums[i]]; ok {
return []int{p, i}
}
hashtable[nums[i]] = i
}
return nil
}
func main() {
a := []int{0, 7, 11, 0}
fmt.Println(twoSum(a, 0))
}
整数反转
思路
首先我们想一下,怎么去反转一个整数?用栈?或者把整数变成字符串,再去反转这个字符串?
这两种方式是可以,但并不好。实际上我们只要能拿到这个整数的 末尾数字 就可以了。
以12345为例,先拿到5,再拿到4,之后是3,2,1,我们按这样的顺序就可以反向拼接处一个数字了,也就能达到 反转 的效果。
怎么拿末尾数字呢?好办,用取模运算就可以了
- 1、将12345 % 10 得到5(取最后一位),之后将12345 / 10(去除最后一位)
- 2、将1234 % 10 得到4,再将1234 / 10
- 3、将123 % 10 得到3,再将123 / 10
- 4、将12 % 10 得到2,再将12 / 10
- 5、将1 % 10 得到1,再将1 / 10
这么看起来,一个循环就搞定了,循环的判断条件是x>0
但这样不对,因为忽略了 负数
循环的判断条件应该是while(x!=0),无论正数还是负数,按照上面不断的/10这样的操作,最后都会变成0,所以判断终止条件就是!=0
有了取模和除法操作,对于像12300这样的数字,也可以完美的解决掉了。
看起来这道题就这么解决了,但请注意,题目上还有这么一句
假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−2^31, 2^31 − 1]。
也就是说我们不能用long存储最终结果,而且有些数字可能是合法范围内的数字,但是反转过来就超过范围了。
假设有1147483649这个数字,它是小于最大的32位整数2147483647的,但是将这个数字反转过来后就变成了9463847411,这就比最大的32位整数还要大了,这样的数字是没法存到int里面的,所以肯定要返回0(溢出了)。
甚至,我们还需要提前判断
上图中,绿色的是最大32位整数
第二排数字中,橘子的是5,它是大于上面同位置的4,这就意味着5后跟任何数字,都会比最大32为整数都大。
所以,我们到【最大数的1/10】时,就要开始判断了
如果某个数字大于 214748364那后面就不用再判断了,肯定溢出了。
如果某个数字等于 214748364呢,这对应到上图中第三、第四、第五排的数字,需要要跟最大数的末尾数字比较,如果这个数字比7还大,说明溢出了。
对于负数也是一样的
上图中绿色部分是最小的32位整数,同样是在【最小数的 1/10】时开始判断
如果某个数字小于 -214748364说明溢出了
如果某个数字等于 -214748364,还需要跟最小数的末尾比较,即看它是否小于8
--From:链接
go代码
package mainimport "fmt"
func reverse(x int) int {
res := 0
for x != 0 {
//取末尾数字
tmp := x % 10
//判断是否大于最大32位整数
if res > 214748364 || (res == 214748364 && tmp > 7) {
return 0
}
//判断是否小于最小32位整数
if res < (-214748364) || (res == (-214748364) && tmp < (-8)) {
return 0
}
res = res*10 + tmp
x /= 10
}
return res
}
func main() {
fmt.Println(reverse(-12300))
}
字符串转换整数 (atoi)
方法1
思路:
1、先处理掉一些非法数值,留下纯数字
2、接着对纯数字字符串进行处理,判断是否溢出
go代码
package mainimport (
"fmt"
"math"
"strings"
)func myAtoi(str string) int {
return convert(clean(str))
}func clean(s string) (sign int, abs string) {
// 先去除首尾空格
s = strings.TrimSpace(s)
if s == "" {
return
}
// 判断第一个字符
switch s[0] {
// 有效的
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
sign, abs = 1, s
// 有效的,正号
case '+':
sign, abs = 1, s[1:]
// 有效的,负号
case '-':
sign, abs = -1, s[1:]
// 无效的,当空字符处理,并且直接返回
default:
abs = ""
return
}
for i, b := range abs {
// 遍历第一波处理过的字符,如果直到第i个位置有效,那就取s[:i],从头到这个有效的字符,剩下的就不管了,也就是break掉
// 比如 s=123abc,那么就取123,也就是s[:3]
if b < '0' || '9' < b {
abs = abs[:i]
// 一定要break,因为后面的就没用了
break
}
}
return
}// 接收的输入是已经处理过的纯数字
func convert(sign int, absStr string) int {
absNum := 0
for _, b := range absStr {
// b - '0' ==> 得到这个字符类型的数字的真实数值的绝对值
absNum = absNum10 + int(b-'0')
// 检查溢出
switch {
case sign == 1 && absNum > math.MaxInt32:
return math.MaxInt32
// 这里和正数不一样的是,必须和负号相乘,也就是变成负数,否则永远走不到里面
case sign == -1 && absNumsign < math.MinInt32:
return math.MinInt32
}
}
return sign * absNum
}func main() {
fmt.Println(myAtoi(" 112233.44.55aabb"))
}
--From 链接
方法2
思路
这道题的难点在于要考虑到各种边界问题,一不留神少了一步判断可能执行就报错了。
根据题目描述,可能会出现各种输入条件,比如:
- " 123"
- " -345 "
- " -+7890"
- "11223344556677889900"
- " -112233.44.55aabb"
等等...
我们总结一下,字符串可能包含下面三种类型:
紫色的第一部分是空格,在转换的时候需要过滤掉
黄色的部分是正负号,如果是正号则忽略,是负号则需要记录这个正负号状态
蓝色是第三部分,这部分字符串中会包含任意字符,但我们只需要"0"到"9"这几个字符
此外,对于11223344556677889900这样的字符串,明显是超长了,所以当字符串大于最大的32位整数,或者小于最小的32位整数,后面就不用判断了。
题目要求是只能存储32位大小的有符号整数,所以不能用long做存储,而且需要提前判断整数的大小。
上图绿色的是最大的32位整数,三个蓝色的数组代表三种不同的输入。
如果是第一种2147483650,这个值本身就比最大32位整数要大了,存到int里面就溢出了,所以提前一位判断,也就是到黄色格子那一位的时候就要判断了。
第一种情况当前的值大于214748364直接返回最大值即可。
对于第二种、第三种情况,如果当的值等于214748364,即前面若干位都一样,再单端判断最后一位,也就是橙色格子那一位。如果最后一位大于等于7,同样也是直接返回最大值。
对于负数也是类似的判断方式:
如果当前值小于-214748364,直接返回最小值即可。
如果当前值等于-214748364,再判断最后一位,如果大于等于8,返回最小值。
总结一下整个执行流程:
- 过滤掉前面若干个空格(如果有的话)
- 判断正号、负号位,如果是负号则记录下状态,表示输入的是负数。
- 循环判断后面的字符串是否是0到9,如果是则累加这个值
- 当前的值跟最大、最小32位整数比较看是否溢出
- 如果是正数,且大于214748364,直接返回最大值
- 如果是正数,且等于214748364,再判断最后一位是否大于7
- 如果是负数,且小于-214748364,直接返回最小值
- 如果是负数,且等于-214748364,再判断最后一位是否大于8
- 循环结束后,根据负号的标志位返回对应的正数或负数
--From:链接
java代码
package test;public class Solution {
public int myAtoi(String str) {
if(strnull) {
return 0;
}
int n = str.length();
int i = 0;
int res = 0;
boolean is_negative = false;
//第一步,跳过前面若干个空格
while(i<n && str.charAt(i)' ') {
++i;
}
//如果字符串全是空格直接返回
if(in) {
return 0;
}
//第二步,判断正负号
if(str.charAt(i)'-') {
is_negative = true;
}
//如果是正负号,还需要将指针i,跳过一位
if(str.charAt(i)'-' || str.charAt(i)'+') {
++i;
}
//第三步,循环判断字符是否在 0~9之间
while(i<n && str.charAt(i)>='0' && str.charAt(i)<='9') {
//'0'的ASCII码是48,'1'的是49,这么一减就从就可以得到真正的整数值
int tmp = str.charAt(i)-48;
//判断是否大于 最大32位整数
if(!is_negative &&(res>214748364 ||(res214748364 && tmp>=7))) {
return 2147483647;
}
//判断是否小于 最小32位整数
if(is_negative &&(-res<-214748364 || (-res-214748364 && tmp>=8))) {
return -2147483648;
}
res = res*10 + tmp;
++i;
}
//如果有负号标记则返回负数
if(is_negative) {
return -res;
}
return res;
}public static void main(String[] args) { // TODO Auto-generated method stub Solution solution =new Solution(); int result = solution.myAtoi(" -s1283472332"); System.out.println(result); }
}
最长公共前缀
C++代码
思路:取第一个字符串中的每个字符与其他字符串的逐个字符比较
#include <iostream> #include <string> #include <vector> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ using namespace std;class Solution
{
public:
string longestCommonPrefix(vector<string> &strs)
{
int strs_Length = strs.size();
string temp,result;
if(strs_Length < 2)
{
return strs[0];
}else
{
for (int j = 1; j <= strs[0].size(); j++)
{
int i = 1;
temp = strs[0].substr(0, j);
result=strs[0].substr(0, j-1);
// cout <<temp<<endl;
while (i < strs_Length)
{
if (temp == strs[i].substr(0, j))
{
i++;
}
else
{
return result;
}
}
}
return temp;
}
}
};int main(int argc, char *argv[])
{
Solution sol;
string s[4] = {"flower","flower","flower","flower"};vector<string> str(s, s +4); //cout <<str[2]<<endl;; cout << sol.longestCommonPrefix(str) << endl; return 0;
}
有效的括号
解题思路
利用一个栈,不断地往里压左括号,一旦遇上了一个右括号,我们就把栈顶的左括号弹出来,表示这是一个合法的组合,以此类推,直到最后判断栈里还有没有左括号剩余。
C代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #define Max 20 #define Stack char int isValid(char * p) { int len=strlen(p);if(len == 1) return 0; int top=1,i; Stack s[Max]; //数组模拟堆栈存储 for(i=0; i<len; i++) { switch(p[i]) { case '(': case '[': case '{': s[top++]=p[i]; //进栈 break; case ')': if(s[top-1]=='(') top--; //出栈 else return 0; break; case ']': if(s[top-1]=='[') top--; //出栈 else return 0; break; case '}': if(s[top-1]=='{') top--;//出栈 else return 0; break; } } if(top==0) return 1; //输出1表示匹配成功 else return 0; //输出0表示匹配失败
}
int main()
{
char s[Max];
printf("请输入括号:");
scanf("%s",s);if(isValid(s) != 0) printf("true"); else printf("false"); return 0;
}
K 个一组翻转链表
参考:链接
这道题考察了两个知识点:
对链表翻转算法是否熟悉
对递归算法的理解是否清晰
在翻转链表的时候,可以借助三个指针:prev、curr、next,分别代表前一个节点、当前节点和下一个节点,实现过程如下所示:
递归方法1
- 1、找到待翻转的k个节点(注意:若剩余数量小于k的话,则不需要反转,因此直接返回待翻转部分的头结点即可)。
- 2、对其进行翻转。并返回翻转后的头结点(注意:翻转为左闭又开区间,所以本轮操作的尾结点其实就是下一轮操作的头结点)。
- 3、对下一轮k个节点也进行翻转操作。
- 4、将上一轮翻转后的尾结点指向下一轮翻转后的头节点,即将每一轮翻转的k的节点连接起来。
C代码
#include <stdio.h> #include <stdlib.h> #include <string.h>typedef struct slist
{
int data;
struct slist *next;
};
struct slist *reverse(struct slist *head,struct slist *tail)
{
struct slist *pre = NULL;
struct slist *next = NULL;
while(head != tail)
{
next = head ->next;
head ->next = pre;
pre = head;
head = next;
}
return pre;
}
struct slist reverseKGroup(struct slist head, int k)
{
if(head == NULL || head ->next == NULL)
return head;
struct slist newHead,tail = head;
int i;
for(i = 0;i < k;i++)
{
//剩余数量小于k的话,不需要反转
if(tail == NULL)
return head;
tail = tail ->next;
}
//反转前K个元素
newHead = reverse(head,tail);
//下一轮的开始的地方就是tail
head ->next = reverseKGroup(tail,k);return newHead;
}
void input(struct slist *head)
{
struct slist *p = head ->next; //p是工作指针
while(p != NULL)
{
printf("%d\t",p ->data);
p = p ->next;
}
}
void create(struct slist *head)
{
//尾插法建立单链表
struct slist r,temp; //r是尾指针,temp是临时结点
int i,x;
r = head;
printf("请输入元素:\n");
scanf("%d",&x);
while(x != 9999)
{
temp = (struct slist *)malloc(sizeof(struct slist));
temp ->data = x;
temp ->next = r ->next;
r ->next = temp;
r = temp;
scanf("%d",&x);
}
}
int main()
{
struct slist *head;//head是头结点head = (struct slist *)malloc(sizeof(struct slist)); head ->next = NULL; create(head); input(head); int k; printf("\n请输入K:"); scanf("%d",&k); head ->next= reverseKGroup(head ->next,k); input(head); return 0;
}
递归方法2
C代码
#include <stdio.h> #include <stdlib.h> #include <string.h>typedef struct slist
{
int data;
struct slist *next;
};
struct slist reverseKGroup(struct slist head, int k)
{
struct slist *cur = head;
int count = 0;
// 找到待翻转的k个节点
while(cur != NULL && count != k)
{
cur = cur ->next;
count++;
}
if(count == k)
{
cur = reverseKGroup(cur,k);
while(count != 0)
{
count--;
struct slist *tmp = head ->next;
head ->next = cur;
cur = head;
head = tmp;
}
head = cur;
}
//若剩余数量小于k的话,则不需要反转,因此直接返回待翻转部分的头结点即可
return head; //head为头指针
}void input(struct slist *head)
{
struct slist *p = head ->next; //p是工作指针
while(p != NULL)
{
printf("%d\t",p ->data);
p = p ->next;
}
}
void create(struct slist *head)
{
//尾插法建立单链表
struct slist r,temp; //r是尾指针,temp是临时结点
int i,x;
r = head;
printf("请输入元素:\n");
scanf("%d",&x);
while(x != 9999)
{
temp = (struct slist *)malloc(sizeof(struct slist));
temp ->data = x;
temp ->next = r ->next;
r ->next = temp;
r = temp;
scanf("%d",&x);
}
}
int main()
{
struct slist *head;//head是头结点head = (struct slist *)malloc(sizeof(struct slist)); head ->next = NULL; create(head); input(head); int k; printf("\n请输入K:"); scanf("%d",&k); head ->next= reverseKGroup(head ->next,k); input(head); return 0;
}
反转链表 II
方法1
C代码
#include <stdio.h> #include <stdlib.h>typedef struct Node
{
int data;
struct Node* next;
} Node;
void create(Node *head)
{
//创建链表 【尾插法】
Node temp,p;
int x;
printf("请输入链表元素:\n");
scanf("%d",&x);
p = head; //p为尾结点
while(x != 9999)
{
temp = (Node *)malloc(sizeof(Node));
temp ->data = x;
p ->next = temp;
p = p ->next;
scanf("%d",&x);
}
temp ->next = NULL;
}
void input(Node *head)
{
Node p = head ->next; //p为工作指针
while(p != NULL)
{
printf("%d\t",p ->data);
p = p ->next;
}
}
int reverseBetween(Node head,int m,int n)
{
if(n < m)
return 0;
if(n == m)
return head;
Node pre,p,q,t;
int i = 1;
p = head->next;
pre = head;
while(i < m)
{
pre = p;
p = p->next;
++i;
}
t = p; //t 记录翻转部分的起点
if(m < n)
{
p = p->next;
++i;
}
while(i <= n)
{
q = p;
p = p->next;
++i;
q->next = pre->next;
pre->next = q; // 每次将第一步找到的结点指向反转后的头结点
}
t->next = p; //将反转的起点next指向反转后面的结点
return head;
}
int main()
{
Node * head;
head = (Node *)malloc(sizeof(Node)); //head为头结点
head ->data = NULL;
head ->next = NULL;
create(head);
input(head);
printf("\n逆置后:\n");
head = reverseBetween(head,2,5);
input(head);
}
方法2
C代码
#include <stdio.h> #include <stdlib.h>typedef struct Node
{
int data;
struct Node* next;
} Node;
void create(Node *head)
{
//创建链表 【尾插法】
Node temp,p;
int x;
printf("请输入链表元素:\n");
scanf("%d",&x);
p = head; //p为尾结点
while(x != 9999)
{
temp = (Node *)malloc(sizeof(Node));
temp ->data = x;
p ->next = temp;
p = p ->next;
scanf("%d",&x);
}
temp ->next = NULL;
}
void input(Node head)
{
Node p = head ->next; //p为工作指针
while(p != NULL)
{
printf("%d\t",p ->data);
p = p ->next;
}
}
int reverseBetween(Node head,int m,int n)
{
if(n < m)
return 0;
if(m == n)
return head; // 不用管的情况
struct Node p = head, tail;
int i ;
for(i = 1; i <= n; i++)
if(i < m) // p指向第m-1个节点位置
p = p->next;
else if(i == m) // tail指向第第m个节点,这个节点反转后处在反转部分的最后一个
tail = p->next;
else //每次将tail后面一个节点拿出来,放在tail后面
{
struct Node item = tail->next;
tail->next = tail->next->next;
item->next = p->next;
p->next = item;
}
return head;
}
int main()
{
Node * head;
head = (Node *)malloc(sizeof(Node)); //head为头结点
head ->data = NULL;
head ->next = NULL;
create(head);
input(head);
printf("\n反转后:\n");
head = reverseBetween(head,2,4);
input(head);
}
扩展:逆置链表
例子: 1 2 3 4 5 6 —— > 6 5 4 3 2 1
将头结点摘下,然后从一个结点开始,依次前插入到头结点的后面【头插法建立链表】,直到最后一个结点结束为止
#include <stdio.h> #include <stdlib.h>typedef struct Node
{
int data;
struct Node* next;
} Node;
void create(Node *head)
{
//创建链表 【尾插法】
Node temp,p;
int x;
printf("请输入链表元素:\n");
scanf("%d",&x);
p = head; //p为尾结点
while(x != 9999)
{
temp = (Node *)malloc(sizeof(Node));
temp ->data = x;
p ->next = temp;
p = p ->next;
scanf("%d",&x);
}
temp ->next = NULL;
}
void input(Node *head)
{
Node *p = head ->next; //p为工作指针
while(p != NULL)
{
printf("%d\t",p ->data);
p = p ->next;
}
}
void reverse(Node * head)
{
Node p ,r; //p为工作指针,r为p的后继
p = head ->next;
head ->next = NULL; //将头结点的next设为NULL
//依次将元素结点摘下
while(p != NULL)
{
r = p ->next; // 暂存p的后继
// 将p结点插入head之后
p ->next = head ->next;
head ->next = p;
p = r;
}
}
int main()
{
Node * head;
head = (Node *)malloc(sizeof(Node)); //head为头结点
head ->data = NULL;
head ->next = NULL;
create(head);
input(head);
printf("\n逆置后:\n");
reverse(head);
input(head);
}
方法2
设定三个指针:pre,p,r指向相邻的结点
#include <stdio.h> #include <stdlib.h>typedef struct Node
{
int data;
struct Node* next;
} Node;
void create(Node *head)
{
//创建链表 【尾插法】
Node temp,p;
int x;
printf("请输入链表元素:\n");
scanf("%d",&x);
p = head; //p为尾结点
while(x != 9999)
{
temp = (Node *)malloc(sizeof(Node));
temp ->data = x;
p ->next = temp;
p = p ->next;
scanf("%d",&x);
}
temp ->next = NULL;
}
void input(Node *head)
{
Node p = head ->next; //p为工作指针
while(p != NULL)
{
printf("%d\t",p ->data);
p = p ->next;
}
}
void reverse(Node * head)
{
//将结点指针翻转
Node p = head ->next ,pre,r = p ->next;
p ->next = NULL; // 处理第一个结点
while(r != NULL) // r为空,说明p为最后一个结点
pre = p;
p = r;
r = r ->next;
p ->next = pre; // p指向前驱pre
}
head ->next =p; // 处理最后一个结点
}
int main()
{
Node * head;
head = (Node *)malloc(sizeof(Node)); //head为头结点
head ->data = NULL;
head ->next = NULL;
create(head);
input(head);
printf("\n逆置后:\n");
reverse(head);
input(head);
}
寻找旋转排序数组中的最小值
方法1:二分搜索
时间复杂度【O(log2 n)】
注意:二分查找只适用于有序的顺序表
1、选取中间位置 mid 的数和 r 位置的数进行比较,若nums[mid] < nums[r],说明最小的数在mid之前,r = mid;反之,令 l = mid+1;
2、重复进行,直到 l >= r。
C代码
#include <stdio.h> #include <stdlib.h>void fun(int *nums,int length)
{
int l = 0;
int r = length - 1;
while(l < r)
{
int mid = (l + r) / 2;
if(nums[mid] > nums[r])
{
l = mid + 1;
}
else
{
r = mid;
}
}
printf("%d",nums[l]);
}
int main()
{
int nums[] = {3,1,2};
int length = sizeof(nums)/sizeof(nums[0]); //求数组长度
fun(nums,length);
return 0;
}
扩展:二分查找
仅适用于有序的顺序表
int Binary_Search(int *a,int key) { //查找key是否在数组a中,如果在返回其位置,若不在,返回-1 int low = 0,high = sizeof(a)/sizeof(a[0]) - 1,mid; while(low <= high) { mid = (low + high) / 2; //取中间位置 if(a[mid] == key) return mid; //查找成功返回所在值 else if(a[mid] > key) high = mid-1; //从前半部分开始查找 else low = mid+1; //从后半部分开始查找 } return -1; }
数组中的第K个最大元素
方法1:先排序后找值
排序选“快速排序”,时间复杂度为【O(log2 n)】
C代码
#include <stdio.h> #include <stdlib.h>int findKthLargest(int* a, int numsSize, int k)
{
// 先排序后查找k的位置
int low=0,high=numsSize-1;
while(low<high)
{
// 快排【从大到小】一趟排序过程
int i=low,j=high,temp=a[i]; // temp为枢纽
while(i<j)
{
while(j>i&&a[j]<=temp)
j--;
if(i<j)
a[i]=a[j];
while(i<j&&a[i]>=temp)
i++;
if(i<j)
a[j]=a[i];
}
a[i]=temp;if(i==(k-1)) // 找到第k大的值 return a[i]; else if(i<(k-1)) // 若小于,则所寻元素一定在枢纽的右边 low=i+1; else // 若大于,则所寻元素一定在枢纽的左边 high=i-1; } return a[low];
}
int main()
{
int a[6] = {1,5,9,7,4,2};
int k;
printf("请输入查找第K大的数:\n");
scanf("%d",&k);
printf("%d",findKthLargest(a,6,k));
return 0;
}
扩展:快排找第K⼩
方法1
// 王道课后题代码 int find(int a[],int low,int high,int k) { int t = a[low]; //t为枢纽 int i = low; int j = high; while(low < high) { while(low < high && a[high] >= t) --high; a[low] = a[high]; while(low < high && a[low] <= t) low++; a[high] = a[low]; } a[low] = t; // 折半查找 if(low == k) { return a[low]); return 0; } else if(low > k) return find(a,i,low - 1,k); else return find(a,low + 1,j,k - low); }
方法2
#include <stdio.h> #include <stdlib.h>int findKthLargest(int* a, int numsSize, int k)
{
// 先排序后查找k的位置
int low=0,high=numsSize-1;
while(low<high)
{
// 快排【从小到大】一趟排序过程
int i=low,j=high,temp=a[i]; // temp为枢纽
while(i<j)
{
while(j>i&&a[j]>=temp)
j--;
if(i<j)
a[i]=a[j];
while(i<j&&a[i]<=temp)
i++;
if(i<j)
a[j]=a[i];
}
a[i]=temp;if(i==(k-1)) // 找到第k小的值 return a[i]; else if(i<(k-1)) // 若小于,则所寻元素一定在枢纽的右边 low=i+1; else // 若大于,则所寻元素一定在枢纽的左边 high=i-1; } return a[low];
}
int main()
{
int a[6] = {1,5,9,7,4,2};
int k;
printf("请输入查找第K小的数:\n");
scanf("%d",&k);
printf("%d",findKthLargest(a,6,k));
return 0;
}
有效的字母异位词
字母异位词:
也就是两个字符串中的相同字符的数量要对应相等。例如,s等于“anagram”,t等于“nagaram”,s和t就互为字母异位词。因为它们都包含有三个字符a,一个字符g,一个字符 m,一个字符 n,以及一个字符 r。而当 s 为 “rat”,t 为 “car”的时候,s 和 t 不互为字母异位词。
解题思路:
一个重要的前提“假设两个字符串只包含小写字母”,小写字母一共也就26个,因此:
知识点:哈希映射
方法1
可以利用两个长度都为26的字符数组来统计每个字符串中小写字母出现的次数,然后再对比是否相等;
C代码
#include <stdio.h> #include <stdlib.h> #include <string.h>int isAnagram(char * s, char * t)
{
//判断两个字符串长度是否相等,不相等则直接返回 false
if(strlen(s) != strlen(t))
return 0;
//若相等,则初始化 26 个字母哈希表,遍历字符串 s 和 t
int A[26] = {0},B[26] = {0}; //哈希映射
int i;
while(s != '\0')
{
A[s - 'a']++;
B[*t - 'a']++;
s++;
t++;
}
//判断两个表是否相同
for(i = 0; i < 26; i++)
{
if(A[i] != B[i])
return 0;
}
return 1;
}
int main()
{
char b[] = "nagaram",a[] = "anagram";
if(isAnagram(a,b) != 0)
printf("True");
else
printf("False");
return 0;
}
方法2
可以只利用一个长度为 26 的字符数组,将出现在字符串 s 里的字符个数加 1,而出现在字符串 t 里的字符个数减 1,最后判断每个小写字母的个数是否都为 0。
C代码
#include <stdio.h> #include <stdlib.h> #include <string.h>int isAnagram(char * s, char * t)
{
//判断两个字符串长度是否相等,不相等则直接返回 false
if(strlen(s) != strlen(t))
return 0;
//若相等,则初始化 26 个字母哈希表,遍历字符串 s 和 t
int A[26] = {0}; //哈希映射
int i;
while(s != '\0)
{
//s 负责在对应位置增加,t 负责在对应位置减少
A[s - 'a']++;
A[*t - 'a']--;
s++;
t++;
}
//如果哈希表的值都为 0,则二者是字母异位词
for(i = 0; i < 26; i++)
{
if(A[i] != 0)
return 0;
}
return 1;
}
int main()
{
char b[] = "nagaram",a[] = "anagram";
if(isAnagram(a,b) != 0)
printf("True");
else
printf("False");
return 0;
}
前K个高频单词
方法1 使用HashMap
1、建一个 HashMap <key = 单词,value = 出现频率>
,遍历整个数组,相应的把这个单词的出现次数 + 1.
这一步时间复杂度是 O(n).
2、用 size = k 的 minHeap 来存放结果,定义好题目中规定的比较顺序
a. 首先按照出现的频率排序;
b. 频率相同时,按字母顺序。
3、遍历这个 map,如果
a. minHeap 里面的单词数还不到 k 个的时候就加进去;
b. 或者遇到更高频的单词就把它替换掉。
时空复杂度分析:
第一步是 O(n),第三步是 nlog(k),所以加在一起时间复杂度是 O(nlogk).
用了一个额外的 heap 和 map,空间复杂度是 O(n).
java代码
package top;import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;class Func {
public List<String> topKFrequent(String[] words, int k) {
// Step 1
Map<String, Integer> map = new HashMap<>(); // 建一个 HashMap <key = 单词,value = 出现频率>
//遍历整个数组,相应的把这个单词的出现次数 + 1
for (String word : words) {
Integer count = map.getOrDefault(word, 0);//次数初始化为0
count++;
map.put(word, count);
}// Step 2 用 size = k 的 minHeap 来存放结果 PriorityQueue<Map.Entry<String, Integer>> minHeap = new PriorityQueue<>(k+1, new Comparator<Map.Entry<String, Integer>>() { @Override public int compare(Map.Entry<String, Integer> e1, Map.Entry<String, Integer> e2) { if(e1.getValue() == e2.getValue()) { //频率相同时,按字母顺序 return e2.getKey().compareTo(e1.getKey()); } //首先按照出现的频率排序 return e1.getValue().compareTo(e2.getValue()); } }); // Step 3 List<String> res = new ArrayList<>(); //遍历这个 map for(Map.Entry<String, Integer> entry : map.entrySet()) { minHeap.offer(entry); if(minHeap.size() > k) { minHeap.poll(); } } while(!minHeap.isEmpty()) { res.add(minHeap.poll().getKey()); } Collections.reverse(res); return res; }
}
public class Solution{
public static void main(String[] args)
{
Func f = new Func();
String str[] = {"the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"};
List<String> res = new ArrayList<>();
int k = 4;
res = f.topKFrequent(str, k);
System.out.println(res);
}
}
方法2:C语言使用uthash
java代码
typedef struct { char* key; int count; UT_hash_handle hh; } WordHash;typedef struct {
WordHash* wh;
} WordsCounter;void WordsCounterAddWord(WordsCounter* obj, char* word)
{
WordHash* cur;
HASH_FIND(hh, obj->wh, word, strlen(word), cur);
if (cur == NULL) {
cur = malloc(sizeof(WordHash));
cur->key = malloc(strlen(word) + 1);
strcpy(cur->key, word);
cur->count = 0;
HASH_ADD_KEYPTR(hh, obj->wh, cur->key, strlen(cur->key), cur);
}
cur->count++;
return;
}WordsCounter* WordsCounterCreate(char** words, int wordsSize)
{
WordsCounter* obj = malloc(sizeof(WordsCounter));
obj->wh = NULL;
for (int i = 0; i < wordsSize; i++) {
WordsCounterAddWord(obj, words[i]);
}
return obj;
}int WordsCounterCompare(WordHash* a, WordHash* b)
{
if (a->count < b->count) {
return 1;
}if (a->count > b->count) { return -1; } return strcmp(a->key, b->key);
}
char** WordsCounterTopKFrequent(WordsCounter* obj, int k, int* returnSize)
{
char** topK = malloc(sizeof(char*) * k);
int topKIndex = 0;
WordHash *wcc;
WordHash *wct;
HASH_ITER(hh, obj->wh, wcc,wct) {
HASH_DEL(obj->wh, wcc);
if (topKIndex < k) {
topK[topKIndex++] = wcc->key;
} else {
free(wcc->key);
}
free(wcc);
}
*returnSize = k;
return topK;
}char** topKFrequent(char** words, int wordsSize, int k, int* returnSize)
{
WordsCounter* obj = WordsCounterCreate(words, wordsSize);
HASH_SORT(obj->wh, WordsCounterCompare);
char** ans = WordsCounterTopKFrequent(obj, k, returnSize);
return ans;
}
重构字符串
java代码
package top;import java.util.ArrayList;
import java.util.List;class Func {
public String reorganizeString(String S) {int[] chs = new int[26]; //记录字符出现的最长长度 int maxLen = 0; for(char ch : S.toCharArray()){ maxLen = Math.max(maxLen, ++chs[ch - 'a']); } int len = S.length(); /* 如果长度为奇数个:那么极端情况是 aaa bc,能够组成 abaca,即最长字符的长度不能超过 len / 2 向上取整,即 不能超过 (len + 1) / 2 如果长度为偶数个,那么极端情况是 aaa bcd,能够组成 abacad,即最长字符的长度不能超过 len / 2,即 6 / 2 综上 总长度为 奇数,最长字符长度不能超过 长度的一半(向上取整) 总长度为 偶数,最长字符串长度不能超过 长度的一半 偶数: len / 2 == (len + 1) / 2 如果 len = 8,那么结果为 4 len / 2 + 1 如果 len = 8,那么结果为 5 奇数: len / 2 如果 len == 7,那么结果为 3 len / 2 + 1 == (len + 1) / 2 (向上取整) 如果 len == 7,那么结果为 4 为了方便,我们可以写成 (len + 1) / 2 */ if(maxLen > (len + 1) / 2){ return ""; } /* 索引位置从 0 开始算 aaa bb ccc 0 1 2 3 4 5 6 7 b a c a c a c b aaaa b ccc 0 1 2 3 4 5 6 7 a b a c a c a c aaaa bbbb 0 1 2 3 4 5 6 7 a b a b a b a b 对于总长度为奇数,如果 字符 长度为 (len + 1) / 2,即向上取整,那么这个字符首先需要放在首尾,即偶数位(因为需要夹紧其他字符,比如 ababa) 对于总长度为偶数,没有什么特别要求,因为无论怎么整,字符的长度最长只能为总长度的一半,那么只要间隔存放,奇数位偶数位没差别(比如 abababab) 综上,我们只需要处理 总长度奇数 的情况即可, 这里我们先写出放在奇数位的条件,chs[i] < (len + 1) / 2 但这里写的话会有问题,因为如果总长度是 偶数的话, 如果存在这么一个情况 aaaabbbb, 那么 每个字符出现的次数都是 4,占了总长度一半,如果写成 chs[i] < (len + 1) / 2,(我们上面说了 对于偶数长度,len / 2 == (len + 1) / 2), 即 只有在 chs[i] < len / 2 的情况才能放在奇数 那么对于 这个 a 来说,由于 chs[i] < len / 2 不成立,因此不会放在奇数,都放在偶数位,然后下一个 b ,同样也不会放在奇数,导致奇数位不会被放置,结果错误 因此,我们需要修改成既能限制 奇数长度的放置,又能不影响 偶数长度的放置 因为对于 奇数长度来说,len / 2 + 1 == (len + 1) / 2,而对于偶数长度来说是不一样的,因此我们采用 len / 2 + 1 这个写法, 这样的话,对于 a 来说,就是 4 < 5 了,a 放置完奇数位,然后奇数位越界,因此 b 会 放置在偶数位,那么结果就是 abababab */ char[] res = new char[len]; int even = 0; int odd = 1; for(int i = 0; i < 26; i++){ //元素个数不为 0 并且 长度 小于 len / 2 + 1,并且奇数位下标还没越界,那么将元素放在奇数位 while(chs[i] > 0 && chs[i] < len / 2 + 1 && odd < len){ res[odd] = (char)(i + 'a'); chs[i]--; odd += 2; } //当 odd 越界了,或者 长度等于 len / 2 + 1,那么就会放在偶数位 while(chs[i] > 0){ res[even] = (char)(i + 'a'); chs[i]--; even += 2; } } return new String(res); }
}
public class Solution{
public static void main(String[] args)
{
Func f = new Func();
String str = "aacab";
String r = f.reorganizeString(str);
System.out.println(r);
}
}
二分查找
C++实现
旋转字符串
C++实现
#include <iostream> #include <string> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ using namespace std;class Solution {
public:
bool rotateString(string s, string goal) {
int s_Length=s.length();if(s==goal) { return true; }else { for(int i=0;i<s_Length-1;i++) { s=s.substr(1)+s.substr(0,1); if(s==goal) { //cout<<s<<endl; return true; } } return false; } }
};
int main(int argc, char *argv[]) {
Solution sol;
if(sol.rotateString("abc","abc"))
{
cout<<"true";
}else
{
cout<<"false";
}
return 0;
}
合并两个有序链表
C++代码
方法1:逐个比较
#include <stdio.h> #include <stdlib.h>//Definition for singly-linked list.
struct ListNode {
int val;
struct ListNode *next;
};//打印链表
void print_list(struct ListNode *list)
{
struct ListNode p=list;
while(p!=NULL)
{
printf("%d ",p->val);
p=p->next;
}
printf("\n");
}
//由数组构建链表
struct ListNode insert(int li)
{
int length=sizeof(li)/sizeof(li[0])+1;
struct ListNode head=NULL,temp,last;//head为链表的头指针,temp为指向当前访问节点的指针for(int i=0;i<length;i++) { //为新插入的节点申请空间 temp=(struct ListNode *)malloc(sizeof(struct ListNode)); temp->val=li[i]; //首次插入时 if(head==NULL) { head=temp; temp->next=NULL; last=temp; }else{ //尾插法 last->next=temp; temp->next=NULL; last=temp; } } return head;
}
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
struct ListNode list,node;//list为链表的头指针
list=(struct ListNode*) malloc(sizeof(struct ListNode));//头节点
node=list;
while(list1!=NULL && list2!=NULL)
{
if(list1->val < list2->val)
{
node->next=list1;
node=node->next;
list1=list1->next;
}else
{
node->next=list2;
node=node->next;
list2=list2->next;
}
}
//如果有一个为空,则直接链接另外一条
if(list1==NULL)
{
node->next=list2;
}else
{
node->next=list1;
}
return list->next;
}int main()
{
int l1[] = {1,2,4}, l2[] = {1,3,4};
struct ListNode list1=NULL,list2=NULL,*list3=NULL;//数组转链表 list1=insert(l1); list2=insert(l2); print_list(list1); print_list(list2); //链表合并(升序) list3=mergeTwoLists(list1,list2); print_list((list3)); return 0;
}
方法2:递归
#include <stdio.h> #include <stdlib.h>//Definition for singly-linked list.
struct ListNode {
int val;
struct ListNode *next;
};//打印链表
void print_list(struct ListNode *list)
{
struct ListNode p=list;
while(p!=NULL)
{
printf("%d ",p->val);
p=p->next;
}
printf("\n");
}
//由数组构建链表
struct ListNode insert(int li)
{
int length=sizeof(li)/sizeof(li[0])+1;
struct ListNode head=NULL,temp,last;//head为链表的头指针,temp为指向当前访问节点的指针for(int i=0;i<length;i++) { //为新插入的节点申请空间 temp=(struct ListNode *)malloc(sizeof(struct ListNode)); temp->val=li[i]; //首次插入时 if(head==NULL) { head=temp; temp->next=NULL; last=temp; }else{ //尾插法 last->next=temp; temp->next=NULL; last=temp; } } return head;
}
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
if(list1NULL)
return list2;
if(list2NULL)
return list1;
if(list1->val <list2->val)
{
list1->next= mergeTwoLists(list1->next,list2);
return list1;
} else
{
list2->next= mergeTwoLists(list1,list2->next);
return list2;
}
}int main()
{
int l1[] = {1,2,4}, l2[] = {1,3,4};
struct ListNode list1=NULL,list2=NULL,*list3=NULL;//数组转链表 list1=insert(l1); list2=insert(l2); print_list(list1); print_list(list2); //链表合并(生序) list3=mergeTwoLists(list1,list2); print_list((list3)); return 0;
}
##唯一摩尔斯密码词
C代码
下面的代码提交后报"内存溢出"的错误
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int uniqueMorseRepresentations(char **words, int wordsSize){
int i,j,result,wordlength;
//定义一个字符串数组(二维)存放编码值
char *a[]={".-","-...","-.-.","-..",".","..-.","--.","....","..",".---","-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-","..-","...-",".--","-..-","-.--","--.."};
//存放编码后的值
char **temp = (char **)malloc(sizeof(char *) * wordsSize);
for (i = 0; i < wordsSize; i++) {
wordlength=strlen(words[i]);
temp[i] = (char *)malloc(sizeof(char) * (12*wordlength)+1);
}
for (i = 0; i < wordsSize; i++) {
j=0;
wordlength=strlen(words[i]);
for(j=0;j<wordlength;j++)
{
strcat(temp[i],a[(int)(words[i][j]-'a')]);
}
printf("%s\n",temp[i]);
}
//判断相同
result=wordsSize;
for(i=0;i<wordsSize;i++)
{
for(j=i+1;j<wordsSize;j++)
{
if(strcmp(temp[i],temp[j])==0)
{
result--;
}
}
}
free(temp);
return result;
}
int main()
{
//输入
char *words[]={"gin", "zen", "gig", "msg"};
//输出
printf("%d\n", uniqueMorseRepresentations(words,4));
return 0;
}