leetcode 题解
两数之和#
方法1:暴力破解#
时间复杂度:O(N2)
空间复杂度:O(1)
go代码#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | package main <p>import "fmt" </p> <p>func twoSum(nums [] int , target int ) [] int {<br> // 暴力破解<br> for i := 0; i < len(nums); i++ {<br> if nums[i] >= target {<br> break <br> }<br> for j := i + 1; j < len(nums); j++ {<br> if nums[i]+nums[j] == target {<br> return [] int {i, j}<br> }<br> }<br> }<br> return nil<br> }<br> func main() {<br> a := [] int {0, 7, 11, 0}<br> fmt.Println(twoSum(a, 0))<br> }<br> </p> |
方法2:Hash表#
时间复杂度:O(N)
空间复杂度:O(N) --空间换时间
思路:创建一个哈希表,对于每一个 x
,我们首先查询哈希表中是否存在 target - x
,然后将 x
插入到哈希表中,即可保证不会让 x
和自己匹配
go代码#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | package main <p> import "fmt" </p> <p> func twoSum(nums []int, target int) []int {<br> // hash表<br> hashtable := map [int]int{}<br> for i := 0; i < len(nums); i++ {<br> if p, ok := hashtable[target-nums[i]]; ok {<br> return []int{p, i}<br> }<br> hashtable[nums[i]] = i<br> }<br> return nil<br> }<br> func main() {<br> a := []int{0, 7, 11, 0}<br> fmt.Println(twoSum(a, 0))<br> }</p> |
整数反转#
思路#
首先我们想一下,怎么去反转一个整数?用栈?或者把整数变成字符串,再去反转这个字符串?
这两种方式是可以,但并不好。实际上我们只要能拿到这个整数的 末尾数字 就可以了。
以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代码#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package main <p> import "fmt" </p> <p> func reverse(x int) int {<br> res := 0<br> for x != 0 {<br> //取末尾数字<br> tmp := x % 10<br> //判断是否大于最大32位整数<br> if res > 214748364 || (res == 214748364 && tmp > 7) {<br> return 0<br> }<br> //判断是否小于最小32位整数<br> if res < (-214748364) || (res == (-214748364) && tmp < (-8)) {<br> return 0<br> }<br> res = res*10 + tmp<br> x /= 10<br> }<br> return res<br> }<br> func main() {<br> fmt.Println(reverse(-12300))<br> }</p> |
字符串转换整数 (atoi)#
方法1#
思路:
1、先处理掉一些非法数值,留下纯数字
2、接着对纯数字字符串进行处理,判断是否溢出
go代码#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | package main <p> import (<br> "fmt" <br> "math" <br> "strings" <br> )</p> <p> func myAtoi(str string) int {<br> return convert(clean(str))<br> }</p> <p> func clean(s string) (sign int, abs string) {<br> // 先去除首尾空格<br> s = strings.TrimSpace(s)<br> if s == "" {<br> return <br> }<br> // 判断第一个字符<br> switch s[0] {<br> // 有效的<br> case '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' :<br> sign, abs = 1, s<br> // 有效的,正号<br> case '+' :<br> sign, abs = 1, s[1:]<br> // 有效的,负号<br> case '-' :<br> sign, abs = -1, s[1:]<br> // 无效的,当空字符处理,并且直接返回<br> default :<br> abs = "" <br> return <br> }<br> for i, b := range abs {<br> // 遍历第一波处理过的字符,如果直到第i个位置有效,那就取s[:i],从头到这个有效的字符,剩下的就不管了,也就是break掉<br> // 比如 s=123abc,那么就取123,也就是s[:3]<br> if b < '0' || '9' < b {<br> abs = abs[:i]<br> // 一定要break,因为后面的就没用了<br> break <br> }<br> }<br> return <br> }</p> <p> // 接收的输入是已经处理过的纯数字<br> func convert(sign int, absStr string) int {<br> absNum := 0<br> for _, b := range absStr {<br> // b - '0' ==> 得到这个字符类型的数字的真实数值的绝对值<br> absNum = absNum<em>10 + int(b- '0' )<br> // 检查溢出<br> switch {<br> case sign == 1 && absNum > math.MaxInt32:<br> return math.MaxInt32<br> // 这里和正数不一样的是,必须和负号相乘,也就是变成负数,否则永远走不到里面<br> case sign == -1 && absNum</em>sign < math.MinInt32:<br> return math.MinInt32<br> }<br> }<br> return sign * absNum<br> }</p> <p> func main() {<br> fmt.Println(myAtoi( " 112233.44.55aabb" ))<br> }<br> </p> |
--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代码#
1 2 3 4 5 6 7 8 9 10 11 12 | public static void main(String[] args) { // TODO Auto-generated method stub Solution solution = new Solution(); int result = solution.myAtoi( " -s1283472332" ); System.out.println(result); } 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++代码#
思路:取第一个字符串中的每个字符与其他字符串的逐个字符比较
1 2 3 4 5 6 7 8 | vector<string> str(s, s +4); //cout <<str[2]<<endl;; cout << sol.longestCommonPrefix(str) << endl; return 0; vector<string> str(s, s +4); //cout <<str[2]<<endl;; cout << sol.longestCommonPrefix(str) << endl; return 0; |
有效的括号#
解题思路#
利用一个栈,不断地往里压左括号,一旦遇上了一个右括号,我们就把栈顶的左括号弹出来,表示这是一个合法的组合,以此类推,直到最后判断栈里还有没有左括号剩余。
C代码#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | 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表示匹配失败 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表示匹配失败 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代码#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | return newHead; return newHead; 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代码#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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; 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代码#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | #include <stdio.h> #include <stdlib.h> <p>typedef struct Node<br> {<br> int data;<br> struct Node* next;<br> } Node;<br> void create(Node *head)<br> {<br> //创建链表 【尾插法】<br> Node <em>temp,</em>p;<br> int x;<br> printf( "请输入链表元素:\n" );<br> scanf( "%d" ,&x);<br> p = head; //p为尾结点<br> while (x != 9999)<br> {<br> temp = (Node *)malloc( sizeof (Node));<br> temp ->data = x;<br> p ->next = temp;<br> p = p ->next;<br> scanf( "%d" ,&x);<br> }<br> temp ->next = NULL;<br> }<br> void input(Node *head)<br> {<br> Node <em>p = head ->next; //p为工作指针<br> while (p != NULL)<br> {<br> printf( "%d\t" ,p ->data);<br> p = p ->next;<br> }<br> }<br> int reverseBetween(Node <em>head, int m, int n)<br> {<br> if (n < m)<br> return 0;<br> if (n == m)<br> return head;<br> Node <em>pre,</em>p,</em>q,</em>t;<br> int i = 1;<br> p = head->next;<br> pre = head;<br> while (i < m)<br> {<br> pre = p;<br> p = p->next;<br> ++i;<br> }<br> t = p; //t 记录翻转部分的起点<br> if (m < n)<br> {<br> p = p->next;<br> ++i;<br> }<br> while (i <= n)<br> {<br> q = p;<br> p = p->next;<br> ++i;<br> q->next = pre->next;<br> pre->next = q; // 每次将第一步找到的结点指向反转后的头结点<br> }<br> t->next = p; //将反转的起点next指向反转后面的结点<br> return head;<br> }<br> int main()<br> {<br> Node * head;<br> head = (Node *)malloc( sizeof (Node)); //head为头结点<br> head ->data = NULL;<br> head ->next = NULL;<br> create(head);<br> input(head);<br> printf( "\n逆置后:\n" );<br> head = reverseBetween(head,2,5);<br> input(head);<br> }<br> </p> |
方法2#
C代码#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | #include <stdio.h> #include <stdlib.h> <p>typedef struct Node<br> {<br> int data;<br> struct Node* next;<br> } Node;<br> void create(Node *head)<br> {<br> //创建链表 【尾插法】<br> Node <em>temp,</em>p;<br> int x;<br> printf( "请输入链表元素:\n" );<br> scanf( "%d" ,&x);<br> p = head; //p为尾结点<br> while (x != 9999)<br> {<br> temp = (Node *)malloc( sizeof (Node));<br> temp ->data = x;<br> p ->next = temp;<br> p = p ->next;<br> scanf( "%d" ,&x);<br> }<br> temp ->next = NULL;<br> }<br> void input(Node <em>head)<br> {<br> Node <em>p = head ->next; //p为工作指针<br> while (p != NULL)<br> {<br> printf( "%d\t" ,p ->data);<br> p = p ->next;<br> }<br> }<br> int reverseBetween(Node <em>head, int m, int n)<br> {<br> if (n < m)<br> return 0;<br> if (m == n)<br> return head; // 不用管的情况<br> struct Node</em> p = head,</em> tail;<br> int i ;<br> for (i = 1; i <= n; i++)<br> if (i < m) // p指向第m-1个节点位置<br> p = p->next;<br> else if (i == m) // tail指向第第m个节点,这个节点反转后处在反转部分的最后一个<br> tail = p->next;<br> else //每次将tail后面一个节点拿出来,放在tail后面<br> {<br> struct Node</em> item = tail->next;<br> tail->next = tail->next->next;<br> item->next = p->next;<br> p->next = item;<br> }<br> return head;<br> }<br> int main()<br> {<br> Node * head;<br> head = (Node *)malloc( sizeof (Node)); //head为头结点<br> head ->data = NULL;<br> head ->next = NULL;<br> create(head);<br> input(head);<br> printf( "\n反转后:\n" );<br> head = reverseBetween(head,2,4);<br> input(head);<br> }</p> |
扩展:逆置链表#
例子: 1 2 3 4 5 6 —— > 6 5 4 3 2 1
#
将头结点摘下,然后从一个结点开始,依次前插入到头结点的后面【头插法建立链表】,直到最后一个结点结束为止
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | #include <stdio.h> #include <stdlib.h> <p>typedef struct Node<br> {<br> int data;<br> struct Node* next;<br> } Node;<br> void create(Node *head)<br> {<br> //创建链表 【尾插法】<br> Node <em>temp,</em>p;<br> int x;<br> printf( "请输入链表元素:\n" );<br> scanf( "%d" ,&x);<br> p = head; //p为尾结点<br> while (x != 9999)<br> {<br> temp = (Node *)malloc( sizeof (Node));<br> temp ->data = x;<br> p ->next = temp;<br> p = p ->next;<br> scanf( "%d" ,&x);<br> }<br> temp ->next = NULL;<br> }<br> void input(Node *head)<br> {<br> Node *p = head ->next; //p为工作指针<br> while (p != NULL)<br> {<br> printf( "%d\t" ,p ->data);<br> p = p ->next;<br> }<br> }<br> void reverse(Node * head)<br> {<br> Node <em>p ,</em>r; //p为工作指针,r为p的后继<br> p = head ->next;<br> head ->next = NULL; //将头结点的next设为NULL<br> //依次将元素结点摘下<br> while (p != NULL)<br> {<br> r = p ->next; // 暂存p的后继<br> // 将p结点插入head之后<br> p ->next = head ->next;<br> head ->next = p;<br> p = r;<br> }<br> }<br> int main()<br> {<br> Node * head;<br> head = (Node *)malloc( sizeof (Node)); //head为头结点<br> head ->data = NULL;<br> head ->next = NULL;<br> create(head);<br> input(head);<br> printf( "\n逆置后:\n" );<br> reverse(head);<br> input(head);<br> }<br> </p> |
方法2#
设定三个指针:pre,p,r指向相邻的结点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | #include <stdio.h> #include <stdlib.h> <p>typedef struct Node<br> {<br> int data;<br> struct Node* next;<br> } Node;<br> void create(Node *head)<br> {<br> //创建链表 【尾插法】<br> Node <em>temp,</em>p;<br> int x;<br> printf( "请输入链表元素:\n" );<br> scanf( "%d" ,&x);<br> p = head; //p为尾结点<br> while (x != 9999)<br> {<br> temp = (Node *)malloc( sizeof (Node));<br> temp ->data = x;<br> p ->next = temp;<br> p = p ->next;<br> scanf( "%d" ,&x);<br> }<br> temp ->next = NULL;<br> }<br> void input(Node *head)<br> {<br> Node <em>p = head ->next; //p为工作指针<br> while (p != NULL)<br> {<br> printf( "%d\t" ,p ->data);<br> p = p ->next;<br> }<br> }<br> void reverse(Node * head)<br> {<br> //将结点指针翻转<br> Node <em>p = head ->next ,</em>pre,</em>r = p ->next;<br> p ->next = NULL; // 处理第一个结点<br> while (r != NULL) // r为空,说明p为最后一个结点<br> pre = p;<br> p = r;<br> r = r ->next;<br> p ->next = pre; // p指向前驱pre<br> }<br> head ->next =p; // 处理最后一个结点<br> }<br> int main()<br> {<br> Node * head;<br> head = (Node *)malloc( sizeof (Node)); //head为头结点<br> head ->data = NULL;<br> head ->next = NULL;<br> create(head);<br> input(head);<br> printf( "\n逆置后:\n" );<br> reverse(head);<br> input(head);<br> }<br> </p> |
寻找旋转排序数组中的最小值#
方法1:二分搜索 #
时间复杂度【O(log2 n)】
注意:二分查找只适用于有序的顺序表
1、选取中间位置 mid 的数和 r 位置的数进行比较,若nums[mid] < nums[r],说明最小的数在mid之前,r = mid;反之,令 l = mid+1;
2、重复进行,直到 l >= r。
C代码#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | #include <stdio.h> #include <stdlib.h> <p> void fun( int *nums, int length)<br> {<br> int l = 0;<br> int r = length - 1;<br> while (l < r)<br> {<br> int mid = (l + r) / 2;<br> if (nums[mid] > nums[r])<br> {<br> l = mid + 1;<br> }<br> else <br> {<br> r = mid;<br> }<br> }<br> printf( "%d" ,nums[l]);<br> }<br> int main()<br> {<br> int nums[] = {3,1,2};<br> int length = sizeof (nums)/ sizeof (nums[0]); //求数组长度<br> fun(nums,length);<br> return 0;<br> }<br> </p> |
扩展:二分查找#
仅适用于有序的顺序表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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代码 #
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | if (i==(k-1)) // 找到第k大的值 return a[i]; else if (i<(k-1)) // 若小于,则所寻元素一定在枢纽的右边 low=i+1; else // 若大于,则所寻元素一定在枢纽的左边 high=i-1; } return a[low]; if (i==(k-1)) // 找到第k大的值 return a[i]; else if (i<(k-1)) // 若小于,则所寻元素一定在枢纽的右边 low=i+1; else // 若大于,则所寻元素一定在枢纽的左边 high=i-1; } return a[low]; |
扩展:快排找第K⼩#
方法1#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // 王道课后题代码 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#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | if (i==(k-1)) // 找到第k小的值 return a[i]; else if (i<(k-1)) // 若小于,则所寻元素一定在枢纽的右边 low=i+1; else // 若大于,则所寻元素一定在枢纽的左边 high=i-1; } return a[low]; if (i==(k-1)) // 找到第k小的值 return a[i]; else if (i<(k-1)) // 若小于,则所寻元素一定在枢纽的右边 low=i+1; else // 若大于,则所寻元素一定在枢纽的左边 high=i-1; } return a[low]; |
有效的字母异位词#
字母异位词:
也就是两个字符串中的相同字符的数量要对应相等。例如,s等于“anagram”,t等于“nagaram”,s和t就互为字母异位词。因为它们都包含有三个字符a,一个字符g,一个字符 m,一个字符 n,以及一个字符 r。而当 s 为 “rat”,t 为 “car”的时候,s 和 t 不互为字母异位词。
解题思路:
一个重要的前提“假设两个字符串只包含小写字母”,小写字母一共也就26个,因此:
知识点:哈希映射
方法1#
可以利用两个长度都为26的字符数组来统计每个字符串中小写字母出现的次数,然后再对比是否相等;
C代码#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | #include <stdio.h> #include <stdlib.h> #include <string.h> <p> int isAnagram( char * s, char * t)<br> {<br> //判断两个字符串长度是否相等,不相等则直接返回 false<br> if ( strlen (s) != strlen (t))<br> return 0;<br> //若相等,则初始化 26 个字母哈希表,遍历字符串 s 和 t<br> int A[26] = {0},B[26] = {0}; //哈希映射<br> int i;<br> while (<em>s != '\0' )<br> {<br> A[</em>s - 'a' ]++;<br> B[*t - 'a' ]++;<br> s++;<br> t++;<br> }<br> //判断两个表是否相同<br> for (i = 0; i < 26; i++)<br> {<br> if (A[i] != B[i])<br> return 0;<br> }<br> return 1;<br> }<br> int main()<br> {<br> char b[] = "nagaram" ,a[] = "anagram" ;<br> if (isAnagram(a,b) != 0)<br> printf ( "True" );<br> else <br> printf ( "False" );<br> return 0;<br> }</p> |
方法2#
可以只利用一个长度为 26 的字符数组,将出现在字符串 s 里的字符个数加 1,而出现在字符串 t 里的字符个数减 1,最后判断每个小写字母的个数是否都为 0。
C代码#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | #include <stdio.h> #include <stdlib.h> #include <string.h> <p> int isAnagram( char * s, char * t)<br> {<br> //判断两个字符串长度是否相等,不相等则直接返回 false<br> if ( strlen (s) != strlen (t))<br> return 0;<br> //若相等,则初始化 26 个字母哈希表,遍历字符串 s 和 t<br> int A[26] = {0}; //哈希映射<br> int i;<br> while (<em>s != '\0)<br> {<br> //s 负责在对应位置增加,t 负责在对应位置减少<br> A[</em>s - 'a' ]++;<br> A[*t - 'a' ]--;<br> s++;<br> t++;<br> }<br> //如果哈希表的值都为 0,则二者是字母异位词<br> for (i = 0; i < 26; i++)<br> {<br> if (A[i] != 0)<br> return 0;<br> }<br> return 1;<br> }<br> int main()<br> {<br> char b[] = "nagaram" ,a[] = "anagram" ;<br> if (isAnagram(a,b) != 0)<br> printf ( "True" );<br> else <br> printf ( "False" );<br> return 0;<br> }</p> |
前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代码#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | // 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; } // 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; } |
方法2:C语言使用uthash #
java代码#
1 2 3 4 5 6 7 8 | if (a->count > b->count) { return - 1 ; } return strcmp(a->key, b->key); if (a->count > b->count) { return - 1 ; } return strcmp(a->key, b->key); |
重构字符串#
java代码#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | 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); } 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); } |
二分查找#
C++实现#
旋转字符串#
C++实现#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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 ; } } 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 ; } } |
合并两个有序链表#
C++代码#
方法1:逐个比较#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | 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; 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; //数组转链表 list1=insert(l1); list2=insert(l2); print_list(list1); print_list(list2); //链表合并(升序) list3=mergeTwoLists(list1,list2); print_list((list3)); return 0; |
方法2:递归 #
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | 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; 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; //数组转链表 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;
}
作者:Hang Shao
出处:https://www.cnblogs.com/pam-sh/p/12757978.html
版权:本作品采用「知识共享」许可协议进行许可。
声明:欢迎交流! 原文链接 ,如有问题,可邮件(mir_soh@163.com)咨询.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)