leetcode 题解

两数之和

方法1:暴力破解

时间复杂度:O(N2)

空间复杂度:O(1)

go代码

package main

import "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 main

import "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 main

import "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 main

import (
"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 && absNum
sign < 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&lt;string&gt; str(s, s +4);
//cout &lt;&lt;str[2]&lt;&lt;endl;;
cout &lt;&lt; sol.longestCommonPrefix(str) &lt;&lt; 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&lt;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 -&gt;next = NULL;
create(head);
input(head);

int k;
printf("\n请输入K:");
scanf("%d",&amp;k);
head -&gt;next= reverseKGroup(head -&gt;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 -&gt;next = NULL;
create(head);
input(head);

int k;
printf("\n请输入K:");
scanf("%d",&amp;k);
head -&gt;next= reverseKGroup(head -&gt;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&lt;(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&lt;(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&lt;Map.Entry&lt;String, Integer&gt;&gt; minHeap = new PriorityQueue&lt;&gt;(k+1, new Comparator&lt;Map.Entry&lt;String, Integer&gt;&gt;() {
        @Override
        public int compare(Map.Entry&lt;String, Integer&gt; e1, Map.Entry&lt;String, Integer&gt; e2) {
            if(e1.getValue() == e2.getValue()) {  
            	//频率相同时,按字母顺序
                return e2.getKey().compareTo(e1.getKey());
            }
            //首先按照出现的频率排序
            return e1.getValue().compareTo(e2.getValue());
        }
    });
    
    // Step 3
    List&lt;String&gt; res = new ArrayList&lt;&gt;();
    //遍历这个 map
    for(Map.Entry&lt;String, Integer&gt; entry : map.entrySet()) {
        minHeap.offer(entry);
        if(minHeap.size() &gt; 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-&gt;count &gt; b-&gt;count) {
    return -1;
}
return strcmp(a-&gt;key, b-&gt;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 &gt; (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] &lt; (len + 1) / 2
    但这里写的话会有问题,因为如果总长度是 偶数的话, 如果存在这么一个情况 aaaabbbb,
    那么 每个字符出现的次数都是 4,占了总长度一半,如果写成 chs[i] &lt; (len + 1) / 2,(我们上面说了 对于偶数长度,len / 2 == (len + 1) / 2),
    即 只有在 chs[i] &lt; len / 2 的情况才能放在奇数
    那么对于 这个 a 来说,由于 chs[i] &lt; len / 2 不成立,因此不会放在奇数,都放在偶数位,然后下一个 b ,同样也不会放在奇数,导致奇数位不会被放置,结果错误
    
    因此,我们需要修改成既能限制 奇数长度的放置,又能不影响 偶数长度的放置
    因为对于 奇数长度来说,len / 2 + 1 == (len + 1) / 2,而对于偶数长度来说是不一样的,因此我们采用 len / 2 + 1 这个写法,
    这样的话,对于 a 来说,就是 4 &lt; 5 了,a 放置完奇数位,然后奇数位越界,因此 b 会 放置在偶数位,那么结果就是 abababab
    */
    char[] res = new char[len];
    int even = 0;
    int odd = 1;
    for(int i = 0; i &lt; 26; i++){
        //元素个数不为 0 并且 长度 小于 len / 2 + 1,并且奇数位下标还没越界,那么将元素放在奇数位
        while(chs[i] &gt; 0 &amp;&amp; chs[i] &lt; len / 2 + 1 &amp;&amp; odd &lt; len){
            res[odd] = (char)(i + 'a');
            chs[i]--;
            odd += 2;
        }
        //当 odd 越界了,或者 长度等于 len / 2 + 1,那么就会放在偶数位
        while(chs[i] &gt; 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&lt;s_Length-1;i++)
		{
    		s=s.substr(1)+s.substr(0,1);
    		if(s==goal)
    		{
    			//cout&lt;&lt;s&lt;&lt;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&lt;length;i++)
{
    //为新插入的节点申请空间
    temp=(struct ListNode *)malloc(sizeof(struct ListNode));
    temp-&gt;val=li[i];
    //首次插入时
    if(head==NULL)
    {
        head=temp;
        temp-&gt;next=NULL;
        last=temp;
    }else{ //尾插法
        last-&gt;next=temp;
        temp-&gt;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&lt;length;i++)
{
    //为新插入的节点申请空间
    temp=(struct ListNode *)malloc(sizeof(struct ListNode));
    temp-&gt;val=li[i];
    //首次插入时
    if(head==NULL)
    {
        head=temp;
        temp-&gt;next=NULL;
        last=temp;
    }else{ //尾插法
        last-&gt;next=temp;
        temp-&gt;next=NULL;
        last=temp;
    }
}
return head;

}

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
if(list1NULL)
return list2;
if(list2
NULL)
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;
}
posted @ 2020-04-23 00:28  PamShao  阅读(694)  评论(0编辑  收藏  举报