一、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.next
ListNode 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;//如果两个长度不一样直接返回false
for(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()]);
}
}