一、k个一组翻转链表
给你链表的头节点 head
,每 k
个节点一组进行翻转,请你返回修改后的链表。
思路:
1.首先得到链表的长度size;然后在size>=k的范围里面进行翻转长度为k的链表。
2.while(size>=k) 在这个循环中,我们需要知道翻转的起点和终点 以及下一次翻转的起点。
pre(起点的前一个) start(起点) end(终点) newStart(新的起点) 并且使用cur节点为每次循环存储新的起点。
3.反转链表的步骤:
3.1.找到前一个节点pre
3.2.找到end,end.next=null(断开链表)
3.3 然后reverse(start) 翻转之后start变成尾结点 end变成头结点。
3.4 重新连接 pre.next=end; pre=start; start.next=newStart cur=newStart
代码:
class Solution {public ListNode reverseKGroup(ListNode head, int k) {if (k == 1) return head;ListNode dummyHead = new ListNode();dummyHead.next = head;ListNode pre = dummyHead;ListNode cur = head;//指向当前节点int size = getSize(cur);while (size >= k) {ListNode start = cur;ListNode end = cur;//end指向k范围内的最后一个for (int i = 1; i < k; i++) {end = end.next;}//下一个头结点=end.nextListNode nextGroupHead = end.next;//断开链条end.next = null;reverseLinkedList(start);pre.next = end;//end变成头结点start.next = nextGroupHead;pre = start;cur = nextGroupHead;size -= k;}return dummyHead.next;}public void reverseLinkedList(ListNode head) {ListNode prev = null;ListNode curr = head;while (curr!= null) {ListNode nextTemp = curr.next;curr.next = prev;prev = curr;curr = nextTemp;}}public int getSize(ListNode head) {int size = 0;while (head!= null) {size++;head = head.next;}return size;}
}
二、最长连续序列
给定一个未排序的整数数组 nums
,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n)
的算法解决此问题。
输入:nums = [100,4,200,1,3,2] 输出:4 解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
思路:
使用哈希表对元素进行处理,先把数组中的元素都加到哈希表中
遍历表中的每一个元素,如果存在contains(num-1),直接continue;
如果num-1不存在,说明这个元素是它最长连续序列的开始,从这里开始计算。
while(set.contains(cur)){cur++;}然后Math.max(res,cur-num+1);
时间复杂度为:O(n)
代码:
class Solution {public int longestConsecutive(int[] nums) {Set<Integer> set=new HashSet<>();for(int num:nums){set.add(num);}int res=0;for(int num:set){//如果集合里面不含有把它小一的元素的话 那么就要从它开始计数int cur=num;if(!set.contains(cur-1)){while(set.contains(cur+1)){cur++;}res=Math.max(res,cur-num+1);}}return res;}
}
筛除素数的方法:
数字包括素数和合数。合数:除了能被1和本身整除外、还能被其他数整除的数。
1.埃氏筛法:
时间复杂度为O(n*loglogn
思想:
1.使用boolean[]数组来对数字进行标记,如果是true表示是非质数,false表示是质数。
2.boolean[0]==boolean[1]=true;(0和1都不是质数)。
3.从2开始遍历,如果遇到boolean[i]==false,表示这个数为质数,那么就要对以该数为因数的合数置true。举个例子:遍历到3的时候,是一个质数。3*3,3*4,3*5,3*6 ...。以它为因数的合数都要置为true。为什么从3*3开始,不从2开始。因为遍历到2这个质数的时候,已经算过了
代码:
int n = 100000;boolean[] prime = new boolean[n + 1];{// 埃氏筛 时间复杂度为O(n*log(logn))for (int i = 2; i <= n; i++) {if (!prime[i]) {// 如果遇到质数for (int j = i; j <= n / i; j++) {prime[i * j] = true;}}}prime[0] = prime[1] = true;}
三、统计不是特殊数字的数字数量
给你两个 正整数 l
和 r
。对于任何数字 x
,x
的所有正因数(除了 x
本身)被称为 x
的 真因数。
如果一个数字恰好仅有两个 真因数,则称该数字为 特殊数字。例如:
- 数字 4 是 特殊数字,因为它的真因数为 1 和 2。
- 数字 6 不是 特殊数字,因为它的真因数为 1、2 和 3。
返回区间 [l, r]
内 不是 特殊数字 的数字数量。
题意简化:
找l->r中,除了本身之外的因数数量为2 的数字。哪些数字符合这样的规则:质数的平方数。
那么本题就是要在l->r中找某个数字的平方根是质数。
思路:
首先将l->r中的平方根范围取出来,如果x是平方数,那么sqrt(x)就是边界。如果sqrt(x)不是整数,那么就要向上取整。右边界应该是向下取整。
代码:
class Solution {int n = 100000;boolean[] prime = new boolean[n + 1];{// 埃氏筛 时间复杂度为O(n*log(logn))for (int i = 2; i <= n; i++) {if (!prime[i]) {// 如果遇到质数for (int j = i; j <= n / i; j++) {prime[i * j] = true;}}}prime[0] = prime[1] = true;}public int nonSpecialCount(int l, int r) {if (r == 1)return 1;int total = r - l + 1;int left = (int)Math.ceil(Math.sqrt(l));int right = (int)Math.floor(Math.sqrt(r));System.out.println("left:"+left+"right:"+right);int res = 0;for (int i = left; i <= right; i++) {int square=i*i;if (!prime[i])res++;}return total - res;}
}
补充:如何去判断一个数字是完全平方数
int value=(int)Math.sqrt(i);
return value*value==i;
四、平方数之和(双指针法)
题意:
给定一个非负整数 c
,你要判断是否存在两个整数 a
和 b
,使得 a^2 + b^2 = c
。
思路:
1.使用双指针法,最开始a和b的范围应该是0和sqrt(c);
2从两边不断向里边逼近,当a越接近与b的时候,a^2+b^2是最大的。
3.如果遇到a^2+b^2>c,说明b要往左移动减小总和;如果遇到a^2+b^2<c,说明a要往右移动增加总和;如果相等,直接返回true
代码:
class Solution {public boolean judgeSquareSum(int c) {int a=0;int b=(int)Math.floor(Math.sqrt(c));//取c的根 并且向下取整while(a<=b){if(a*a==c-b*b)return true;else if(a*a>c-b*b)b--;else a++;}return false;}
}
五、快乐数(快慢指针/哈希表)
快乐数 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
1 <= n <= 231 - 1
思路:
1.对于一个n,下一个数最大为810。(因为n最大为十位数,假设每一位置上都为9,也是9*9*10=810)
2.因此下一个数只能从1-810的范围里面出现,如果重复出现了,那么必然走到了循环里。就看重复出现之前有没有出现1。
3.这种思路可以借助于HashSet来实现,也可以通过快慢指针判断是否有环。
HashSet
class Solution {public boolean isHappy(int n) {HashSet<Integer> set=new HashSet<>();while(n!=1&&!set.contains(n)){set.add(n);n=happyNum(n);}return n==1;}public int happyNum(int num){int res=0;while(num>0){res+=Math.pow(num%10,2);num/=10;}return res;}
}
快慢指针:
class Solution {public boolean isHappy(int n) {int slow=n,fast=happyNum(n);while(slow!=fast){slow=happyNum(slow);fast=happyNum(happyNum(fast));}return slow==1;}public int happyNum(int num){int res=0;while(num>0){res+=Math.pow(num%10,2);num/=10;}return res;}
}
六、哈希表一一映射关系 (单词规律/同构字符串)
单词规律:
给定一种规律 pattern
和一个字符串 s
,判断 s
是否遵循相同的规律。
这里的 遵循 指完全匹配,例如, pattern
里的每个字母和字符串 s
中的每个非空单词之间存在着双向连接的对应规律。
pattern = "abba" s ="dog dog dog dog" 这样会出现多对一的情况
pattern = "aaaa" s ="dog fish fish dog" 这样会出现一对多的情况
思路:
因此只建立一个map集合是无法正确映射他们的关系的,可能会出现一对多或者多对一的情况。好的办法就是建立两个map集合,实现一一映射的关系。
eg:pattern=abba s=dog dog dog dog;a不在map1中,添加<a,dog>;dog不在map2中,添加<dog,a>;b不在map1中,此时b对应的是dog。但是在map2中,dog只对应了a。因此不符合。
代码:
class Solution {public boolean wordPattern(String pattern, String s) {if(s.length()==1)return true;String[] spl=s.split(" ");Map<String,Character> sTop=new HashMap<>();Map<Character,String> pTos=new HashMap<>();if(pattern.length()!=spl.length)return false;//如果两个长度不一样直接返回falsefor(int i=0;i<spl.length;i++){char ch=pattern.charAt(i);String str=spl[i];if((pTos.containsKey(ch)&&!(pTos.get(ch).equals(str)))||(sTop.containsKey(str)&&!(sTop.get(str)==ch))){return false;}pTos.put(ch,str);sTop.put(str,ch);}return true;}
}
七、分数到小数
给定两个整数,分别表示分数的分子 numerator
和分母 denominator
,以 字符串形式返回小数 。
如果小数部分为循环小数,则将循环的部分括在括号内。
输入:numerator = 4, denominator = 333 输出:"0.(012)"
思路:
两个数相除结果可能有:整数、有限小数、无限循环小数。
1.先判断是否是正数if(a%b==0)return xxx
2.如果结果不是整数,那么继续判断是有限小数还是无限循环小数:
2.1 有限小数:最终a*=10 a%b==0的 比如说2/5,2*10/5=4 a%b==0;
2.2 无限循环小数:会存在循环节的,10/6666666666
注意:
1.要判断两个数字的正负号
2.判断循环节的时候,使用哈希表进行判断。如果新计算出的余数在哈希表中出现过,那么就说明从map.get(a)。这个位置到现在是循环节。
3.String.format("%s(%s)",x,x);%s为占位符,表示转换后的格式为x(x);
代码:
/**
如何处理:先把整数处理出来,如果有余数可能是有限小数或者无限循环小数
1.先拼接整数
2.如果可以除尽 就说明是有限小数
3.不能除尽 找寻环节*/
class Solution {public String fractionToDecimal(int numerator, int denominator) {long a=(long)numerator;long b=(long)denominator;if(a%b==0)return String.valueOf(a/b);StringBuilder sb=new StringBuilder();if(a<0^b<0)sb.append('-');a=Math.abs(a);b=Math.abs(b);//取整数sb.append(a/b).append(".");//取小数a%=b;//使用map集合 记录每一次余数的位置 用来判断是否存在循环节Map<Long,Integer> map=new HashMap<>();while(a!=0){int index=sb.length();map.put(a,index);a*=10;sb.append(a/b);a%=b;//如果map集合中遇到了重复的余数 说明后面的数字一定是循环的if(map.containsKey(a)){int c=map.get(a);return String.format("%s(%s)",sb.substring(0,c),sb.substring(c,sb.length()));}}return sb.toString();}
}
八、两个列表的最小索引总和
假设 Andy 和 Doris 想在晚餐时选择一家餐厅,并且他们都有一个表示最喜爱餐厅的列表,每个餐厅的名字用字符串表示。
你需要帮助他们用最少的索引和找出他们共同喜爱的餐厅。 如果答案不止一个,则输出所有答案并且不考虑顺序。 你可以假设答案总是存在。
思路:
使用哈希表,如果另一个人喜爱的餐厅里面也包含遍历的该餐厅,那么就计算下标和。这里计算最小下标和的时候有一个小技巧:如果sum<min,清空集合:list.clear(),list.add(num),min=sum;这样保证了在集合中的元素是下标和最小的餐厅
代码:
class Solution {public String[] findRestaurant(String[] list1, String[] list2) {Map<String,Integer> map=new HashMap<>();for(int i=0;i<list2.length;i++){map.put(list2[i],i);}//将每一个餐厅的下标和都放进map中List<String> res=new ArrayList<>();int min=Integer.MAX_VALUE;for(int i=0;i<list1.length;i++){if(map.containsKey(list1[i])){int index=i+map.get(list1[i]);if(index<min){res.clear();res.add(list1[i]);min=index;}else if(index==min){res.add(list1[i]);}}}return res.toArray(new String[res.size()]);}
}