剑指offer1

1.打印从1到最大的n位数

输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。

1. 用返回一个整数列表来代替打印
2. n 为正整数,0 < n <= 5

思路:输入3,则最大999;输入2,则最大99;输入1,最大9(输入0也是)

返回整数列表,所以用数组

public class Main {
    public static void main(String []args) {
        Scanner input = new Scanner(System.in);
        int n=input.nextInt();
        printNumbers (n);

    }
        public static void printNumbers (int n) {
            int size = 0;
            for(int i =0;i <n;i ++){ size = 10 * size +9; }
            int[] array = new int[size];
            for(int i=0;i<array.length;i++){
                array[i]=i+1;
                System.out.println(array[i]);
            }
        }
    }

2.剪绳子(进阶版)

长度为 n 的绳子剪成 m 段( m 、 n 都是整数, n > 1 并且 m > 1 , m <= n ),每段绳子的长度记为 k[1],...,k[m] 。请问 k[1]*k[2]*...*k[m] 可能的最大乘积是多少?

由于答案过大,请对 998244353 取模。

数据范围:2\leq n \leq 10^{14}2≤n≤1014

进阶:空间复杂度 O(1)O(1) , 时间复杂度 O(logn)O(logn)

思路:均值不等式,均分最大,求导方法得出奇异点在e,通过计算得出3最大,所以尽可能每段为3。当最后=1时,从前面拿出一段再2*2;当最后为2,直接乘上

1.暴力法

public class Solution {
  public long cutRope (long number) {
       if(number<=3) return number-1;
     
        long res=1;
        while(number>4){
            res=res*3;
            res=res%998244353;
            number=number-3;
        }
        return res*number%998244353;
    }
}

 2.快速幂

思路:   1.若num>0且是偶数,则基底变平方;幂指数/2()
                 if(num%2==0)  base = base * base % mod;
                  num >>= 1;
                if(num%2==1){ res = res * base % mod;}(奇数停止,不是1)
             2.若奇数,
                if(num%2==1){ res = res * base % mod;}只乘了一个base,res=base罢了
               base^2
              num>>=1奇数这里的关键在base 

(a^2)^(b/2)         b为偶数
(a^2)^(b/2)*a     b为奇数
 //若num为奇数则res=base*res%mod;(单独的a被乘上,且这里的base)
// base=base*base;//这里base在以平方级增长
//num>>=1;
public class Solution {
       long mod = 998244353;
    public long cutRope (long number) {
       if(number<=3) return number-1;
        long a = number/3;//8...2
        long b = number%3;//8...2
        if(b==0) return Pow(3, a) % mod;
        else if(b==1) return Pow(3, a-1) * 4 % mod;
        else return Pow(3, a) * 2 % mod;
        }
 //开始快速幂,快速乘积
       public long Pow(long base, long num){
        long res = 1;
        while(num > 0){
           if(num%2==1){ res = res * base % mod;}
             base = base * base % mod;
            num >>= 1;
        }
        return res;
    }}

3.调整数组顺序使奇数位于偶数前面(二)

要求:时间复杂度 O(n),空间复杂度 O(1)

1)复制法:返回函数的数组不占空间

//准备一个数组复制法
public class Solution {
    public int[] reOrderArrayTwo (int[] array) {
        int n=array.length-1;
        int m=0;
        int[] ch=new int[array.length];
        for(int i=0;i<array.length;i++){
            if(array[i]%2==0){
               ch[n]=array[i];
                 n=n-1;
             }else{
                ch[m]=array[i];
                  m++;}
        }
        return ch;}
}

2)双指针法

思路:左边奇数指针,右边偶数指针
//左边指针没有遇到偶数,一直向右移动
//遇到偶数,则不动。然后右边指针开始向左移动,若没遇到奇数一直向左,
//遇到奇数了,则左右奇偶互换(注:奇偶互换的前提左边<右边,因为防止jjjooo已经换好的情况)

public class Solution {
  public int[] reOrderArrayTwo (int[] array) {
        int high=array.length-1;
        int low=0;
        while(low<high){
              while(array[low]%2==1)    low++;
              while(array[high]%2==0)   high--;
            if(low<high){
                int a=array[low];
                array[low]=array[high];
                array[high]=a; }
         }
        return array;
      }
    }

4.字符流中第一个不重复的字符

请实现一个函数用来找出字符流中第一个只出现一次的字符。

string caseout = "";

1.读入测试用例字符串casein

2.如果对应语言有Init()函数的话,执行Init() 函数

3.循环遍历字符串里的每一个字符ch {

Insert(ch);

caseout += FirstAppearingOnce()

}

2. 输出caseout,进行比较。

如果当前字符流没有存在出现一次的字符,返回#字符。

输入"google"  返回值:"ggg#ll"

string caseout = "";

1.读入测试用例字符串casein

2.如果对应语言有Init()函数的话,执行Init() 函数

3.循环遍历字符串里的每一个字符ch {

Insert(ch);

caseout += FirstAppearingOnce()

}

2. 输出caseout,进行比较
public class Solution {
    List<Character> list = new ArrayList<>();//储存传过来的字符流
    Map<Character,Integer> map = new LinkedHashMap<>(16);//用哈希判断是否重复
    public void Insert(char ch)
    {
 /* Insert 函数是用来接收每次的从字符流传递过来的一个字符举例:
    第一次:Insert 传递过来的一个字符: g
    就要在 FirstAppearingOnce 函数中去判断当前第一个只出现一次的字符(g) 显然是 g
    第二次:Insert 传递过来一个字符: o
    就要在 FirstAppearingOnce 函数中去判断当前第一个只出现一次的字符(go) 显然是还是 g
      */
    if(!map.containsKey(ch)) map.put(ch,1);
        else map.put(ch,map.get(ch)+1);
        list.add(ch);//这是统计输入字符流
    }
  //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {
          char emp='#';
        for(char s:list){
            if(map.get(s)==1) {emp=s;break;}//这是找第一个次数char
            }
            return emp; 
    }
}

5.孩子们的游戏(圆圈中最后剩下的数)

1)公式

思路:设f(n,m)为n个数取m的结果
                      0 1 2 3 4 5 。。。。m-1 m m+1 m+2 ......n-1 
           f(n,m)=0 1 2 3 4 5 ....m-2 m m+1 m+2 ...n-1 
  重新编号求f(n-1,m)
                n-m n-m+1     n-3 n-2 0 1 2 ......n-1-m n-m-1【m-1肯定在其中】
         从f(n-1,m)倒推求f(n) 我们可知f(n)=f(n-1,m)+m%n(注意这是n-1倒推n,所以i从2开始)

public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if (n <1 || m <1) return -1;
        int last=0;
        for(int i=2;i<=n;i++){
            last=(last+m)%i;//注意这里是i不是n!!!!!!
        }
      return last;
    }
    

2)队列

思路:把全部元素放入队列,在size!=1的时候,不断计数:不是m-1,从队列头取出元素放队尾;是则弹出

/*
  add      增加一个元索              如果队列已满,则抛出一个IIIegaISlabEepeplian异常
  remove   移除并返回队列头部的元素   如果队列为空,则抛出一个NoSuchElementException异常
  element  返回队列头部的元素        如果队列为空,则抛出一个NoSuchElementException异常
  offer    添加一个元素并返回true    如果队列已满,则返回false
  poll     移除并返问队列头部的元素   如果队列为空,则返回null
  peek     返回队列头部的元素        如果队列为空,则返回null
  put      添加一个元素              如果队列满,则阻塞
  take     移除并返回队列头部的元素   如果队列为空,则阻塞
*/
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
       if (n <= 1 || m == 0)     return -1;
       Queue<Integer> queue=new LinkedList<>();
       for(int i=0;i<n;i++){ queue.add(i);}
        while(queue.size()!=1){
            for(int i=0;i<m-1;i++){
                queue.add(queue.remove());//将前面的取出来加在末尾
            }
            queue.remove();
        }
        return  queue.remove(); }}

3)环形链表模拟圆圈

import java.util.*;
class ListNode {
    int val;
    ListNode next = null;
    ListNode(int val) {this.val = val;}
}
//题目要求从0编号从1计数,再加上链表++k
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
       if (n <= 0 || m<= 0)     return -1;
        ListNode head=new ListNode(0);
        ListNode node=head;
       for(int i=1;i<n;i++){ 
           node.next=new ListNode(i);
           node=node.next;}
           node.next=head;//这时候node的下一个点是头结点,从头结点开始报1.。
            int k=0;
        while(node.next!=node){//一个点首尾不互联
           if(++k==m){//这时候k=m-1
               node.next=node.next.next;
               k=0; }
            else node=node.next;
        }
        return node.val;
    }   
}

6. 左旋转字符串

对于一个给定的字符序列  S ,请你把其循环左移 K 位后的序列输出。例如,字符序列 S = ”abcXYZdef” , 要求输出循环左移 3 位后的结果,即 “XYZdefabc” 。

思路:没懂它的意思,应该是S+S+S...从k开始数S字符串长吧,所以n>S.size的时候,n=n%S.size,这样处理。当n=S.size,返回原字符串

也可:原理:YX = (XTYT)T

 //1.误差判断
//2.对n进行处理
//3.取0..n的字符串,n+1..的字符串,翻转拼接

public String LeftRotateString(String str,int n) {
        char[] ch=str.toCharArray();
     if (str == null || str.length() == 0 || n <= 0)
            return str;
        if (n >= str.length())//例外就是可能n会大于字符串的长度,因此还需要处理
            n = n % str.length();
        String s1="";String s2="";
        for(int i=0;i<n;i++){
            s1=s1+ch[i];
        }
        for(int i=n;i<ch.length;i++){
            s2=s2+ch[i];
        }
        return s2+s1;
    }

7.和为S的两个数字

//注意数组为空
import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
           int low=0;
        int high=array.length-1;
        ArrayList<Integer> array1=new  ArrayList<Integer>();
           if(array.length==0) return array1;
        while(low<high){
            if(array[low]+array[high]>sum)  high--;
            else if(array[low]+array[high]<sum)  low++;
            else{
                array1.add(array[low]); 
                   array1.add(array[high]);
                break;}
        }       
        return array1; }}

8.和为S的连续正数序列

输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序(挺简单的,从1开始找)

import java.util.ArrayList;
//双指针法:(某种意义接近滑动窗口)两个起点1和2
//利用等差公式求和SUM,若和等目标,则添加进数组,右指针移动
//                   若和<目标,则右指针右移
//...                若和>目标,则左指针右移
public class Solution {
    public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
       ArrayList<ArrayList<Integer> > result = new ArrayList<>();
             int plow = 1,phigh = 2;
        while(plow<phigh){
          int cur=(phigh + plow) * (phigh - plow + 1) / 2;
           if(cur==sum){
              ArrayList<Integer> list= new ArrayList<>();
                for(int i=plow;i<=phigh;i++){
                    list.add(i);   }
                result.add(list);
                 phigh++; }
            if(cur<sum) phigh++;
            if(cur>sum) plow++;
          }
        return result;
    }
}

9.丑数

 思路:准备一个数组,存放丑数结果。
          每放进一个数就分别乘2,3,5,得到的结果比较大小,然后再放进去
         怎么储存三个数组?
         1 2 3 4 5 6 8
          2  4  6  8 10
          3  6  9..12 15
          5 10 15  20 25

  【这里也可看做三个数组是数组中元素分别乘2,3,5得来的,每个数字分别乘235可以看做独立的,当进去元素那么指针后移】
    //1进去后(此时3个指针均在1处),1分别乘2,3,5取最小值=2,p2指针指向2,
    //其他均指向1,这时
候是2*2,1*3,1*5比较大小

 public int GetUglyNumber_Solution(int index) {
    if(index<7) return index;
        int[] res = new int[index];//存放结果的数组
        int a2=0; int a3=0;int a5=0;
        res[0]=1;
         for(int i=1;i<index;i++){
             res[i]=Math.min(res[a2]*2,Math.min(res[a3]*3,res[a5]*5));
               if(res[i]==res[a2]*2) a2++;
               if(res[i]==res[a3]*3) a3++;
               if(res[i]==res[a5]*5) a5++;
        }
        return res[index-1];
    }

9. 数组中出现次数超过一半的数字

1)哈希

 keySet(),containKey,

import java.util.*;
public class Solution {
    //哈希
    public int MoreThanHalfNum_Solution(int [] array) {
        if(array.length==1) return array[0];
        HashMap<Integer,Integer> map=new HashMap<Integer,Integer>();
       
        for(int i=0;i<array.length;i++){
            if(!map.containsKey(array[i])) map.put(array[i],1);
           else{
            int temp=map.get(array[i]);
                temp=temp+1;
              map.put(array[i],temp); }
         }
        for(int resKey : map.keySet()){
            if(map.get(resKey)*2>array.length)  return resKey;
        }
       return -1;
    }
}

2)排序法

3)投票法

思路:数组中存在众数,那么众数一定大于数组的长度的一半。如果两个数不相等,就消去这两个数。最坏情况下每次消去众数和非众数,那么最终留下的一定是众数

具体做法:
 1.维护一个候选众数candidate和它出现的次数count。初始时candidate为首元素,count为 0;

 2..我们遍历数组array所有元素,对于每个元素 x,进行如下判断:

1)x==candidate,则count++;

2) x!=candidate时分为两种情况,count>0时,count--;

                                                     count=0时,令candidate=x,count=1

  public int MoreThanHalfNum_Solution(int [] array) {
      if (array==null||array.length==0) return -1;
        int candidate = array[0], cnt = 1;
      for(int i = 1; i < array.length; i++){
           if(candidate==array[i])  cnt++;
           else if(cnt>0)   cnt--;
           else { 
                candidate = array[i];
                cnt = 1; }}
           return candidate;}
}

  10.调整数组顺序使奇数位于偶数前面(一)

       所有的奇数位于数组的前面部分,所有的偶数位于数组的后面部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变

1)暴力解法

import java.util.*;
/*解法粗暴,
     遍历一遍数组,然后把奇数放在一个链表里,偶数放在一个链表里,
然后addAll把偶数连在奇数链表的后面,再遍历一遍总链表把值存在新建的数组里面就可以返回了。
*/
public class Solution {
  public int[] reOrderArray (int[] array){
        if(array==null||array.length<=0) return array;
       ArrayList<Integer> odd=new ArrayList<Integer>();
       ArrayList<Integer> eve=new ArrayList<Integer>();
        for(int i=0;i<array.length;i++){
            if(array[i]%2==1) odd.add(array[i]);
            else eve.add(array[i]);
        }
         odd.addAll(eve);
        int[] result = new int[odd.size()];
        for(int j = 0;j < result.length;j++){
            result[j] = odd.get(j);
        }
        return result;}
    }

2) 双指针(O(N),O(N)创立并返回辅助数组)

    左指针从头开始扫,遇到奇数,则加上新数组(遇到偶数则不管)

   右指针从尾开始扫,遇到偶数,则从尾加入数组

    一个while(left<len && right>=0)即可

3)尾插法,双指针(时间复杂度O(n^2),空间负载度O(1))

思路:一个指针i=0,又一个指针寻找奇数,当奇数下标为j时,则i+1-j-1后移,j插入i的位置,i指针移动(从思路看j=0,j=1是特例,但是编码时可以避开)

public class Solution {

    public int[] reOrderArray (int[] array){
            int i = 0;
       for(int j=0;j<array.length;j++){
           if(array[j]%2==1){
               int temp=array[j];
               for(int k=j;j>i;j--){ array[j]=array[j-1];}
               array[i]=temp;
               i++;}
       }
        return array;
    }
}

11.替换空格

请实现一个函数,将一个字符串s中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

1) 遍历法(我们需要对长度为n的字符串遍历一次,时间复杂度为O(n),需要存储长度为n的字符串,空间复杂度为O(n)

 public String replaceSpace (String s) {
        char[] str=s.toCharArray();
          String s1="";
       for(int i=0;i<str.length;i++){
           if(str[i]==' ') s1=s1+"%20";
           else s1=s1+str[i];
        }
        return s1;
    }

 2)从头到尾扫描字符串,每一次碰到空格的时候做替换。由于是把一个字符替换成3个字符,我们必须要把后面的所有字符都后移两个字节,否则就有两个字符被覆盖了。故假设字符串的长度是n,对每个空格字符,需要移动后面O(n)个字符,因此对含有O(n)个空格的字符串而言总的时间效率是O(n^2)


3)双指针法

import java.util.*;
    /*
      对空格进行计数,扩充原数组长度,新长度=原+空格*2,再从后往前替换
     */

public class Solution {

public String replaceSpace (String s) {
      if (s == null || s.length() == 0) return "";
        int spaceNum = 0;
        int m = s.length();
      for (int i = 0; i < m; i++) {
          char c = s.charAt(i);
          if (c == ' ') spaceNum++;}
        //p1指向原字符串末尾
        int p1 = m - 1;
        //p2指向替换之后字符串的末尾,spaceNum为空格数,3是"%20"的长度
        int p2 = p1 + spaceNum * 2;
        char[] tmp = new char[p2+1];
        for (int i = 0; i < s.length(); i++) { tmp[i] = s.charAt(i);}
         //当p1和p2指向同一位置时,说明已经替换完毕
        while (p1 >= 0 && p1 != p2) {
            if (tmp[p1] == ' ') {
                tmp[p2--] = '0';
                tmp[p2--] = '2';
                tmp[p2--] = '%';
            }else {
                tmp[p2--] = tmp[p1];
            }
            p1--;
        }
  
        return new String(tmp);
    }
}

 12.第一个只出现一次的字符

在一个长为 字符串中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1.(从0开始计数)

数据范围:0 \le n \le 100000≤n≤10000,且字符串只有字母组成。

要求:空间复杂度 O(n)时间复杂度 O(n)

1)哈希映射

注意:哈希映射的key是无序的,所以我们引入LinkedHashMap

//应该还可以再精简  
public int FirstNotRepeatingChar(String str) {
        LinkedHashMap<Character,Integer> map=new LinkedHashMap<Character,Integer>();
        char[] ch=str.toCharArray();
         for(int i=0;i<ch.length;i++){
             if(!map.containsKey(ch[i])) map.put(ch[i],1);
            else{
                int temp=map.get(ch[i]);
                temp=temp+1;
                map.put(ch[i],temp);
            }
         }
        int k=0;
        for(int i=0;i<ch.length;i++){//这里容易错误
            if(map.get(ch[i])==1) return k;
            else k++;
        }
        return -1; }
import java.util.*;
public class Solution {
    public int FirstNotRepeatingChar(String str) {
        LinkedHashMap<Character,Integer> map=new LinkedHashMap<Character,Integer>();
     
         for(int i=0;i<str.length();i++){
             if(!map.containsKey(str.charAt(i))) map.put(str.charAt(i),1);
            else{
                int temp=map.get(str.charAt(i));
                temp=temp+1;
                map.put(str.charAt(i),temp);
            }
         }
        int k=0;
        for(int i=0;i<str.length();i++){
            if(map.get(str.charAt(i))==1) return k;
            else k++;
        }
        return -1;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值