剑指Offer题
1.重建二叉树
示例1
输入
[1,2,3,4,5,6,7],[3,2,4,1,6,5,7]
返回值
{1,2,5,3,4,6,7}
前置知识:
二叉树的前序遍历:根左右 二叉树的中序遍历:左根右 二叉树的的后序遍历:左右根
解题思路
首先我们肯定是要通过递归来实现重建的
递归就是从大问题转化为小问题的过程
根据二叉树的特性,二叉树的左右子树也是也是一颗二叉树(所以他是最适合递归的数据结构 不管是遍历还是建立)
所以我们现在要来分解了 根据前序遍历我们知道数组第一位都是根节点 然后是左子树右子树部分
1 (根节点)234 (左子树)567(右子树) 根左右
那么我们就从中序遍历找到根节点的位置
324(左子树) 1 (根节点)657(右子树) 左根右
再次递归
root.left= b(234 ,324)
root.right=b(567 ,657)
退出的位置就是 传入的是空数组就return null;
这里我用到Arrays的函数来截取数组,当然也可以通过下标来截取,但是参数就要添加。
Arrays.copyOfRang
import java.util.Arrays;
Arrays.copyOfRange(T[ ] original,int from,int to)
将一个原始的数组original,从下标from开始复制,复制到上标to,生成一个新的数组。
注意这里包括下标from,不包括上标to。
2.构建斐波那契数列
题目描述
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0,第1项是1)。
//递归
public class Solution {
public int Fibonacci(int n) {
if(n==0)return 0;
if(n==1)return 1;
return Fibonacci(n-1)+Fibonacci(n-2);
}
}
//时间复杂度O(2^n)运行时间:852ms 占用内存:9484k
//动态规划
public class Solution {
public int Fibonacci(int n) {
int []dp = new int[n+1];
if(n==0)return 0;
if(n==1)return 1;
dp[0]=0;
dp[1]=1;
for(int i=2;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
}
//时间复杂度O(N) 空间复杂度O(n) 运行时间 13ms
3.两个栈实现队列
我的理解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZMuyFyNI-1618561745425)(C:\Users\77308\AppData\Roaming\Typora\typora-user-images\image-20201110140607502.png)]
事实上
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-obV9SiUp-1618561745427)(C:\Users\77308\AppData\Roaming\Typora\typora-user-images\image-20201110140835802.png)]
import java.util.Stack;
public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int node) {
stack1.push(node);
}
public int pop() {
if(stack2.empty()){
while(!stack1.empty()){
stack2.push(stack1.pop());
}}
return stack2.pop();
}
4.跳台阶
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
public class Solution {
public int JumpFloor(int target) {
int []dp = new int [target+1];
if(target==1)
return 1;
dp[0]=1;
dp[1]=1;
for(int i = 2;i<= target; i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[target];
}
}
//时间复杂度O(n) 空间复杂度O(n)
// ###继续优化
// 发现计算f[5]的时候只用到了f[4]和f[3], 没有用到f[2]...f[0],所以保存f[2]..f[0]是浪费了空间。
// 只需要用3个变量即可。
int Fibonacci(int n) {
if (n == 0 || n == 1) return n;
int a = 1, b = 1, c;
for (int i=2; i<=n; ++i) {
c = a + b;
a = b;
b = c;
}
return c;
}
// 时间复杂度:O(n)空间复杂度:O(1)
5.矩形覆盖
我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
比如n=3时,2*3的矩形块有3种覆盖方法:
public class Solution {
public int RectCover(int target) {
if(target==1||target==2)return target;
int a = 1, b = 2,c =0;
for (int i=3; i<=target; ++i) {
c = a + b;
a = b;
b = c;
}
return c;
}
}
//遇到这种问题都是要找规律的
//n = 1 的时候
//只能横着覆盖,一种
//n = 2 的时候
//可以横着和竖着覆盖,两种
//n = 3 的时候
//第三级横着覆盖,用了一级,剩下 n = 2,有两种覆盖方法
//第三季竖着覆盖,用了两级,剩下 n = 1,有一种覆盖方法
//总共有 3 种
//n = 4 的时候
//第 4 级横着覆盖,用了一级,剩下 n = 3,有三种覆盖方法
//第 4 级竖着覆盖,用了两级,剩下 n = 2,有两种覆盖方法
//总共有 5 种方法
//n = n 的时候
//第 n 级横着覆盖,用了一级,剩下 n = n - 1,所以关注第 n - 1 种有几种覆盖方法
//第 n 级竖着覆盖,用了两级,剩下 n = n - 2,所以关注第 n - 2 种有几种覆盖方法
//总和为两种情况的总和
// 所以 f(n)=f(n-1)+f(n-2)
6.1的个数
输入一个整数,输出该数32位二进制表示中1的个数。其中负数用补码表示。
public class Solution {
public int NumberOf1(int n) {
int count;
while(n != 0){
n&=(n-1);count++;
}
return count;
}
}
//牛逼之处不用我多说了吧
//这里我说一个其他的知识点 负数向右移动的话是在前面补1 因为负数在计算机中是以补码表示的
7.反转链表
输入一个链表,反转链表后,输出新链表的表头。
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
import java.util.Vector;
public class Solution {
public ListNode ReverseList(ListNode head) {
ListNode cur=head;
ListNode pre=null;
ListNode nex=null;
while(cur!=null){
nex=cur.next;
cur.next=pre;
pre=cur;
cur=nex;
}
return pre;
}
}
//暴力做法就是创建一个Vector 遍历链表然后将数据存到矢量数组中,为什么用Vectoe因为他是动态数组,(放在栈中好点) 然后再建立一个链表
// 正常做法是建立3个变量 pre cur nex nex=cur.next;
// cur.next=pre;
// pre=cur;
// cur=nex;
8.合并两个链表
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
ListNode head = new ListNode(-1);
ListNode pre =head;
while(list1!=null&&list2!=null){
if(list1.val<list2.val){
pre.next=list1;
pre = list1;
list1=list1.next;
}
else{
pre.next=list2;
pre = list2;
list2=list2.next;
}
}
if(list1==null){
pre.next=list2;
}
if(list2==null)
pre.next=list1;
return head.next;
}
}
//思路就是。。。。简单的思路
//我错了就是在于if里不能2个if 因为这样第一个if执行后 就可以继续执行下一个if 但是我们要的是每次移动就判断是否有空。
//还有就是结束后,直接pre=list就行 ,因为他是有序的,后面都是排好的
9.数组中只出现一次的数
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字
解题思路:
1、第一次循环将所有的数当做key存到map中,然后map.get(key) ++
第二个循环找出map中,value为1的数就是了
O(n)
O(n)
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
HashMap<Integer,Integer> num = new HashMap<>();
for (int i = 0; i < array.length; i++) {
if(num.containsKey(array[i]))
num.put(array[i],2);
else
num.put(array[i],1);
}
int count = 0;
for (int i = 0; i < array.length; i++) {
if(num.get(array[i])==1){
if(count == 0){
num1[0]=array[i];
count++;
}
else{
num2[0]=array[i];
}
}
}
}
2、高级思路: 因为相同的数异或为0 一个数异或0也为0
所以第一次循环将所有的数都相互异或一遍,那么里面相同的数就会没了。
结果肯定是两个没有相同的数异或的结果,那么我们想要拿到这个数就要想办法将这个结果分开
最好的办法就是将原来的数组分为2个数组,分别放这两个数,那么我们就可以像第一步那样拿到这个数,怎么分呢
第一步异或的结果肯定是这两个数异或后的,那么这个二进制中为1的就是他们不同的地方(不同异或为1),所以我们就根据这个标识,再一个循环把这个数组中的第x位为1的和不为1的分开,那么这两个数就不在同一个数组里了,就可以求出来了、
怎么找到这个标识,就是找第几位为1,我们可以&1,然后将1左移1位,找到&1等于1的就行,比如判断第3位是不是1 就&4(100),
O(n) O(1)
//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
public class Solution {
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
int result = 0;
for (int i = 0; i < array.length; i++) {
result ^= array[i];
}
//找出从右边开始第一个不同的1
int first1 = 1;
while((result & first1)==0){
first1 = first1 << 1;
}
num1[0] = 0;
num2[0] = 0;
for (int i = 0; i < array.length; i++) {
if((first1 & array[i])==0){
num1[0] ^= array[i];
} else{
num2[0] ^= array[i];
}
}
}
}
10.和为S的连续正数序列
1 暴力法
O(N^2)
O(N^2)
public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
for (int i = 1; i <= sum/2; i++) {
int temp = 0;
ArrayList<Integer> curentResult = new ArrayList<>();
for (int j = i; j <=sum/2+1 ; j++) {
temp+= j;
curentResult.add(j);
if(temp == sum){
result.add(curentResult);
break;
}
if(temp > sum) break;
}
}
return result;
}
2.前缀法
O(N^2)
O(N)
public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
ArrayList<ArrayList<Integer>> results = new ArrayList<>();
int[] index = new int[sum+1];
int tem = 0;
for (int i = 1; i <= sum; i++) {
tem += i;
index[i]=tem;
}
for (int i = 1; i < sum/2+2; i++) {
for (int j = i+1; j < sum/2+2 ; j++) {
if((index[j]-index[i-1]) == sum){
ArrayList<Integer> result = new ArrayList<>();
for (int k = i; k <= j; k++) {
result.add(k);
}
results.add(result);
}
}
}
return results;
}
3.滑动窗口法
O(N)
O(1)
ArrayList<ArrayList<Integer>> results = new ArrayList<>();
int i = 1, j= 1;
int tmp = 0;
while(i<=sum/2){
if(sum == tmp){
ArrayList<Integer> result = new ArrayList<>();
for (int k = i; k < j; k++) {
result.add(k);
}
results.add(result);
tmp+=j;
j++;
}
else if (sum > tmp){
tmp+=j;
j++;
}else{
tmp-=i;
i++;
}
}
return results;
}
11.和为S的两个数字
给定一个数组,返回两个数字和为sun且乘积最小的两个数字。
和上题也很像,滑动窗口法(前提是有序的)
因为数组是有序的,所以可以用双指针,指向数组的首尾,具体步骤如下: 1.初始化:指针i指向数组首, 指针j指向数组尾部 如果arr[i] + arr[j] == sum , 说明是可能解 否则如果arr[i] + arr[j] > sum, 说明和太大,所以--j 否则如果arr[i] + arr[j] < sum, 说明和太小,所以++i
时间复杂度:O(n) 空间复杂度:O(1)
ArrayList<Integer> result = new ArrayList<>();
int i= 0 , j = array.length-1;
int min = Integer.MAX_VALUE;
while(i<array.length&&j>-1){
if((array[i]+array[j])==sum){
if((array[i]*array[j])<min){
min = (array[i]*array[j]);
result.clear();
result.add(array[i]);
result.add(array[j]);
}
i++;
}else if ((array[i]+array[j])<sum){
i++;
}else{
j--;
}
}
return result;
12.JZ45扑克牌顺子
时间复杂度O(N) 空间复杂度O(1)
思路: 找出数组中最大值和最小值(0不算)
最大-最小-除0外的数的数字就是可以放0的空位 max-min-1-(numbers.length-2-count);
如果空位<0代表有重复数字 falese
如果空位<0的数量可以填充 true
如果空位大于0的数量 false
适用于不只是5个数 且负数也行
public class Solution {
public boolean isContinuous(int [] numbers) {
int count = 0 , min = Integer.MAX_VALUE,max = Integer.MIN_VALUE;
for (int i = 0; i < numbers.length; i++) {
if(numbers[i]==0){
count++;}
if(numbers[i] > max && numbers[i]!=0){
max = numbers[i];
}
if(numbers[i] < min && numbers[i]!=0){
min = numbers[i];
}
}
int emptyNum = max-min-1-(numbers.length-2-count);
if(emptyNum<0){
return false;
}
if(count-emptyNum>=0){
return true;
}
return false;
}
}
13.孩子们的游戏(圆圈中最后剩下的数)
这道题真的是典型的递归题,递归就要数学归纳,找出规律,真的是我的弱项,好难!
两种做法
一、模仿法,使用一个队列,看题解说是约瑟夫环的问题,首先我们先将所有人的序号存进队列中,然后(m-1)% list.size 移除队列
就是完全模仿游戏来进行
时间复杂度 O(n)
空间复杂度 O(n)
import java.util.List;
import java.util.ArrayList;
public class Solution {
public int LastRemaining_Solution(int n, int m) {
if(n == 0 || m == 0)
return -1;
List<Integer> list = new ArrayList<>();
for (int i = 0; i < n; i++) {
list.add(i);
}
int bt = 0;
while(list.size()>1){
bt = (bt+m-1)%list.size();
list.remove(bt);
}
return list.get(0);
}
}
第二种就是递归了 首先归纳一下 我的理解就是递归每一层的结果都对上一层有帮助且公式是一样的,就是要总结一个公式。
那么,我们可以思考 当剩余n个的时候 删除的是 (m-1)%n 那么我们怎么根据下一层的结果来推断这一层的结果呢? 如果我们知道
下一层法f(n-1,m)最终剩下的的是第x个的话,那么,我们这一层剩下的就是从(m)%n作为第一个开始之后的第x个,以此类推,就可以得出一个公式 f(n)=f(m/n+f(n-1)%n) 当然必须要有出口 f(1) = 0 只有一个的话是不会剩下人的
http://zhedahht.blog.163.com/blog/static/2541117420072250322938/
import java.util.List;
import java.util.ArrayList;
public class Solution {
public int f (int n,int m){
if (n == 1) return 0;
return (m%n+f(n-1,m))%n;
}
public int LastRemaining_Solution(int n, int m) {
if(n == 0 || m == 0)
return -1;
return f(n,m);
}
}
别人写的这就是小细节!~!!
class Solution {
public:
int LastRemaining_Solution(unsigned int n, unsigned int m)
{
if(n==0)
return -1;
if(n==1)
return 0;
else
return (LastRemaining_Solution(n-1,m)+m)%n;
}
};
14.求1+2+3+...+n
题目重述:求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
结题思路: 因为不能使用for循环 那么和判断, 那么就要使用什么东西来替代判断和循环, 循环就使用递归,判断就使用&& ||
这样就简单了,条件1&&条件2 如果条件1成立那么就只会执行条件一,发生短路。
15.数组中重复的数字 -- Java 实现
这是比较容易的实现,使用map,但是
时间复杂度O(n)
空间复杂度O (n)
import java.util.*;
public class Solution {
public boolean duplicate(int numbers[],int length,int [] duplication) {
if(numbers == null || length == 0){
return false;
}
Arrays.sort(numbers);
for(int i=0;i<length-1;i++){
if(numbers[i] == numbers[i+1]){
duplication[0] = numbers[i];
return true;
}
}
return false;
}
}
16.表示数值的字符串
来玩正则表达式吧!~ 就是正则表达式的应用
public class Solution {
public boolean isNumeric(char[] str) {
String s = String.valueOf(str);
return s.matches( "[\\+-]?\\d*(\\.?\\d+)?([eE][\\+-]?\\d+)?" );
}
}
^ 和 美元符号框定正则表达式,它指引这个正则表达式对文本中的所有字符都进行匹配。如果省略这些标识,那么只要一个字符串中包含一个数字这个正则表达式就会进行匹配。如果仅包含 ^ ,它将匹配以一个数字开头的字符串。如果仅包含$ ,则匹配以一个数字结尾的字符串。
[-+]?
正负号后面的 ? 后缀表示这个负号是可选的,表示有0到1个负号或者正号
\\d*
\d的含义和[0-9]一样。它匹配一个数字。后缀 * 指引它可匹配零个或者多个数字。
(?:\\.\\d*)?
(?: …)?表示一个可选的非捕获型分组。* 指引这个分组会匹配后面跟随的0个或者多个数字的小数点。
(?:[eE][+\\-]?\d+)?
这是另外一个可选的非捕获型分组。它会匹配一个e(或E)、一个可选的正负号以及一个或多个数字。
17.字符流中第一个不重复的字符
解题思路:用一个数组(模拟哈希,因为字符就128个,没有哈希冲突,所以不需要用哈希表)其次就是我们要的是第一个没有重复的,就是先进先出,那就是队列了。所以这里我们用到 数组(hash) 和 队列
首先
先判断字符是否在数组里,不在将字符放到队列中,在的话不管
接着将队列出队,判断数组的值是否是1,是就返回答案 (这里是因为我们后面可能又出现这个字符了,但是先前已经放进队列了,需要判断一下)
如果队列空了还是没有找到就是没有
时间复杂度O128 空间复杂度 O128因为只要数组中有了就不加进队列的、
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Queue;
public class Solution {
int[] charCnt = new int[128];
Queue<Character> queue = new LinkedList<Character>();
//Insert one char from stringstream
public void Insert(char ch)
{
if(charCnt[ch]++==0){
queue.add(ch);
}
}
//return the first appearence once char in current stringstream
public char FirstAppearingOnce()
{
while(!queue.isEmpty()){
if(charCnt[queue.peek()]==1){
return queue.peek();
}else{
queue.remove();
}
}
return '#';
}
}
18.链表中环的入口结点
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-StO5Qktd-1618561745431)(image-20210125220212202.png)]
解题思路
哈希判断:节点一直next,将节点的值放入set中,如果next有null,那么就是空,如果next的值存在set中就退出,这个值得节点就是环的入口,浪费一个n的空间
On On
import java.util.HashSet;
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead)
{
HashSet<Integer> nums = new HashSet<>();
while(pHead!=null){
if(nums.contains(pHead.val)){
return pHead;
}
else{
nums.add(pHead.val);
}
pHead=pHead.next;
}
return null;}
}
快慢节点: 一个走快一个走慢速度是两倍,肯定会相遇的,那么设相遇的节点位置为p,我们就可以列一个等式2(a+b)=a+nb+(n-1)c,可以算出 a == c,那么 从起始点 和 p 一起走,就会遇到的话就是q。!!!!!精彩
On O1
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead)
{
if(pHead == null || pHead.next == null){
return null;
}
ListNode l = pHead;
ListNode r = pHead;
while(l != null && r != null){
l=l.next.next;
r=r.next;
if(l==r){ //先求出相遇的点
l=pHead;
while(l!=r){
l=l.next;
r=r.next;
}
return l;
}
}
return null;
}
}
19.二叉树的下一个结点 --Java实现
解题思路:简单实现模拟法,先找到根节点,然后中序遍历。将遍历的节点放到List中,在接着找到数列中的下一个就是了。
On On
/*
public class TreeLinkNode {
int val;
TreeLinkNode left = null;
TreeLinkNode right = null;
TreeLinkNode next = null;
TreeLinkNode(int val) {
this.val = val;
}
}
*/
import java.util.ArrayList;
public class Solution {
ArrayList<TreeLinkNode> nums = new ArrayList<>();
public TreeLinkNode GetNext(TreeLinkNode pNode)
{ TreeLinkNode n = pNode;
while(pNode.next!=null){
pNode = pNode.next;
}
TreeLinkNode head = pNode;
zx(head);
for(int i=0;i<nums.size();i++){
if(nums.get(i) == n){
return i==nums.size()-1?null:nums.get(i+1);
}
}
return null;
}
public void zx(TreeLinkNode pNode){
if(pNode!=null){
zx(pNode.left);
nums.add(pNode);
zx(pNode.right);
}
}
}
分析法:
//有右子树 下一个节点就是他的右子树的最左节点 //没有右子树,看它是否是父节点的左子树 //是的话就是其父节点 //不是的话就是找到当前节点的父节点,且它是父节点的左子树 如果没有找到就是null
On O1
import java.util.ArrayList;
public class Solution {
ArrayList<TreeLinkNode> nums = new ArrayList<>();
public TreeLinkNode GetNext(TreeLinkNode pNode)
{
//有右子树 下一个节点就是他的右子树的最左节点
//没有右子树,看它是否是父节点的左子树
//是的话就是其父节点
//不是的话就是找到当前节点的父节点,且它是父节点的左子树 如果没有找到就是null
if( pNode.right!=null){
TreeLinkNode tmp = pNode.right;
while(true){
if(tmp.left==null){
return tmp;
}else{
tmp = tmp.left;
}
}
}
else{
while(pNode.next != null){
TreeLinkNode parent = pNode.next;
if(parent.left == pNode){
return parent;
}
pNode = parent;
}
return null;
}
}
}
20.把二叉树打印成多行 --Java实现
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
输入
{8,6,10,5,7,9,11}
返回值
[[8],[6,10],[5,7,9,11]]
解题思路: 首先这道题肯定是可以用层级遍历来做的,重点就是层级来存储到一个list中,因为每一层就放在一个队列中。所以有递归和非递归两种做法,两种并没有优劣之分。但是空间复杂度就有的说了,层级的话,最开始的思路是给每个节点设置一个map,存储他的层级,并且按照顺序创建的的队列,如果层级>=result的size,那么就要新建一个,否则就根据他的层级放在哪个list中。
方法一:非递归 On On
import java.util.ArrayList;
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.Queue;
import java.util.LinkedList;
import java.util.*;
public class Solution {
ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer> > results = new ArrayList<>();
HashMap<TreeNode,Integer> deeps = new HashMap<>();
Queue<TreeNode> queue = new LinkedList<>();
if(pRoot != null){
queue.offer(pRoot);
deeps.put(pRoot,0);}
while(!queue.isEmpty()){
TreeNode temp = queue.poll();
int deep = deeps.get(temp);
if(temp.left!=null){
queue.offer(temp.left);
deeps.put(temp.left,deep+1);
}
if(temp.right!=null){
queue.offer(temp.right);
deeps.put(temp.right,deep+1);
}
if(deep>=results.size()){
ArrayList<Integer> list = new ArrayList<>();
list.add(temp.val);
results.add(list);
}else{
ArrayList<Integer> list = results.get(deep);
list.add(temp.val);
}
}
return results;
}
}
方法二:递归 递归可以保证递归的每层就是他的层级,使用前序遍历是为了保证创建的时候按从上到下来分层。
import java.util.ArrayList;
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.Queue;
import java.util.LinkedList;
import java.util.*;
public class Solution {
ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer>> lists = new ArrayList<>();
if (pRoot == null) {
return lists;
}
dfs(pRoot, 0, lists);
return lists;
}
private void dfs(TreeNode pRoot, int deep, ArrayList<ArrayList<Integer>> lists) {
if (pRoot == null) {
return;
}
if (deep >= lists.size()) {
ArrayList<Integer> list = new ArrayList<>();
list.add(pRoot.val);
lists.add(list);
} else {
ArrayList<Integer> list = lists.get(deep);//和非递归不同的是他不是按顺序来的,所以要get
list.add(pRoot.val);
}
dfs(pRoot.left, deep + 1, lists);
dfs(pRoot.right, deep + 1, lists);
}
}
方法三: 非递归 On O1(妙) 根据出列后,队列中新添加进去的数目来确定下一层的大小,从而不用记录层数。
import java.util.Queue;
import java.util.LinkedList;
import java.util.*;
public class Solution {
ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer> > results = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
if(pRoot == null) return results;
queue.offer(pRoot);
while(!queue.isEmpty()){
int deep = queue.size();
ArrayList<Integer> list = new ArrayList<>();
for(int i =0 ; i< deep;i++){
TreeNode temp = queue.poll();
list.add(temp.val);
if(temp.left!=null){
queue.offer(temp.left);
}
if(temp.right!=null){
queue.offer(temp.right);
}
}
results.add(list);
}
return results;
}
}
21.重建二叉树
经典的题目 如果不会就丢人了
递归的经典使用 思路就是把每次递归都看成是建一个二叉树 然后将前序数组放入 中序数组放入 ,找到根节点
退出标志就是其中有一个遍历数组的长度是0 这里 Arrays.copyOfRange(ar,from,to) 范围是上闭合 [)
/**
* Definition for binary tree
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
import java.util.*;
public class Solution {
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
if(pre.length==0||in.length==0){
return null;
}
TreeNode head = new TreeNode(pre[0]);
int index = 0;
for(int i = 0; i< in.length; i++){
if(in[i]==pre[0]) index=i;
}
head.left = reConstructBinaryTree(Arrays.copyOfRange(pre,1,index+1),
Arrays.copyOfRange(in,0,index));
head.right = reConstructBinaryTree(Arrays.copyOfRange(pre,index+1,pre.length),
Arrays.copyOfRange(in,index+1,in.length));
return head;
}
}
21.反转链表
基本题 用三个指针解决
public class Solution {
public ListNode ReverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
ListNode next = null;
while(cur!=null){
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
}
22.二维数组中的查找
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
输入
7,[[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]]
返回值
true
解题思路: 真的精彩,一般有序的查找我们都是使用二分的嘛,但是这是二维的,显然就不好二分了,那么我们可以使用什么方法呢?
这里有一个信息,每一行都是递增,这不重要,每一列都递增,这就厉害了,我们可以先把位置放到左下或者右上,保证他是在这一行、列里最大的,那么比他小的是不是就是这一列的前面,比他大的只能从下一行找了,就是这样,最坏 m行+n列就能找到!!!
O(m+n) O(1)
平时我们不是要O(m*n)呀
public class Solution {
public boolean Find(int target, int [][] array) {
int i = 0;
int j = array[0].length-1;
for(i = 0; i<array.length;i++ ){
for(j = array[i].length-1; j>-1;j--){
if(target> array[i][j]){
break;
}
else if(target<array[i][j]){
continue;
}
else{
return true;
}
}
}
return false;
}
}
23.从尾到头打印链表
1·listNode 是链表,只能从头遍历到尾,但是输出却要求从尾到头,这是典型的"先进后出",我们可以想到栈!
2·也可以想到反转链表
3·最好是递归
1.用栈 On On
import java.util.List;
import java.util.ArrayList;
import java.util.*;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
Deque nums = new ArrayDeque<Integer>();
while(listNode!=null){
nums.push(listNode.val);
listNode=listNode.next;
}
ArrayList<Integer> results = new ArrayList<>();
while(!nums.isEmpty()){
results.add((Integer)nums.pop());
}
return results;
}
}
2.反转链表 On O1
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> results = new ArrayList<>();
if(listNode == null)return results;
ListNode pre=null;
ListNode cur=listNode;
ListNode nex=listNode.next;
while(cur!=null){
nex = cur.next;
cur.next = pre;
pre = cur;
cur =nex;
}
while(pre !=null){
results.add(pre.val);
pre = pre.next;
}
return results;
}
}
3.用递归 好像简单点 On O1
public class Solution {
//1.用栈 On On
//2.反转链表 On O1
//3.用栈 好像简单点 On O1
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> results = new ArrayList<Integer> ();
reverse(listNode,results);
return results;
}
public void reverse(ListNode listNode , ArrayList<Integer> results){
if(listNode!=null){
reverse(listNode.next,results);
results.add(listNode.val);
}
}
}
24.调整数组顺序使奇数位于偶数前面
这里要求的是排序,可是要保证稳定性,那么快排就没用,只能用冒泡 插入 等 所以时间复杂度是On^2 O1
但是牺牲空间的话是可以达到On On,用一个数组存储 我的想法是使用一个双端队列 偶数从尾巴进去,奇数从头进去
类似插入
public class Solution {
public void reOrderArray(int [] array) {
int i = 0;
for (int j=0; j<array.length; ++j) {
if (array[j]%2==1) {
int tmp = array[j];
for (int k=j-1; k>=i; --k) {
array[k+1] = array[k];
}
array[i++] = tmp;
}
}
}
}
类似冒泡
空间
25.链表中倒数第k个结点
解题思路 快慢指针 快指针先走k步,再一起走,快指针走到最后慢指针就是倒数的位置。
On O1
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
ListNode low = head;
int i =0;
ListNode fast = head;
while(fast!=null){
if(i==k){
fast = fast.next;
low = low.next;
}
else{
i++;
fast=fast.next;
}
}
if(i==k)return low;
else return null;
}
}
26.树的子结构 (必须二刷)
这道题有够恶心,看的时候感觉很简单,写起来 哎 一言难尽 以后写代码还是一步一步解析,不要贪图求快,重点 && 和 || 的短路作用
&& 第一个条件错了就不会下面的条件 || 是可以的 所以用来替代 if判断 ,坑爹的是我看的题解都是|| && 一个还好递归进行 你就傻了
解题思路 :
- 我首先的思路就是 两个树都先序遍历 放进队列中,然后看子队列是否是母队列 Om+m Om+m
- 使用递归,先序遍历父树,
- 在父树的每个节点都判断是否含有子树,一旦有的话直接返回是
- 判断根节点是否相同
- 再递归判断左节点 右节点
- 退出标志 子树为空 true 父树为空 子树不为空 父子节点不同
- 退出标志 节点为空或者找到我们要的子树
Om+m Om+m
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
return
(root1 != null&& root2!= null)&&(
isSubTree(root1,root2)
|| HasSubtree(root1.left,root2)
|| HasSubtree(root1.right,root2));
}
public boolean isSubTree(TreeNode root1,TreeNode root2){
if(root2 == null) return true;
if(root1 == null||root1.val!=root2.val)return false;
return isSubTree(root1.left,root2.left)
&&isSubTree(root1.right,root2.right);
}
}
26.包含min函数的栈 要求 O1
解题思路: 想要 O1那么就要空间换时间,因为我们找到最小的话,用排序的话就要On,那么我们可以用双栈,一个栈存值,一个是用来存最小值,因为栈的进出是顺序的,所以每次出栈是无所谓的,只要在进栈的时候将值和栈顶最小值对比,如果小就放进去,如果不是就继续将栈顶的值再压一次,其实这里也可以优化,只有是最小值或者是小于最小的才压,其余不压,出的时候只有等于最小值才出。O1 O(n-*)
import java.util.Stack;
public class Solution {
Stack<Integer> stack = new Stack<>();
Stack<Integer> minval = new Stack<>();
public void push(int node) {
stack.push(node);
if (minval.isEmpty()){
minval.push(node);
}
else{
if(node<=minval.peek()){
minval.push(node);
}else{
minval.push(minval.peek());
}
}
}
public void pop() {
stack.pop();
minval.pop();
}
public int top() {
return stack.peek();
}
public int min() {
return minval.peek();
}
}
//空间复杂度 O(n-*)
import java.util.Stack;
public class Solution {
Stack<Integer> stack = new Stack<>();
Stack<Integer> minval = new Stack<>();
public void push(int node) {
stack.push(node);
if (minval.isEmpty()){
minval.push(node);
}
else{
if(node<=minval.peek()){
minval.push(node);
}
}
}
public void pop() {
if(stack.peek()<=minval.peek()){
minval.pop();
}
stack.pop();
}
public int top() {
return stack.peek();
}
public int min() {
return minval.peek();
}
}
26.包含min函数的栈 要求 O1
解题思路: 想要 O1那么就要空间换时间,因为我们找到最小的话,用排序的话就要On,那么我们可以用双栈,一个栈存值,一个是用来存最小值,因为栈的进出是顺序的,所以每次出栈是无所谓的,只要在进栈的时候将值和栈顶最小值对比,如果小就放进去,如果不是就继续将栈顶的值再压一次,其实这里也可以优化,只有是最小值或者是小于最小的才压,其余不压,出的时候只有等于最小值才出。O1 O(n-*)
import java.util.Stack;
public class Solution {
Stack<Integer> stack = new Stack<>();
Stack<Integer> minval = new Stack<>();
public void push(int node) {
stack.push(node);
if (minval.isEmpty()){
minval.push(node);
}
else{
if(node<=minval.peek()){
minval.push(node);
}else{
minval.push(minval.peek());
}
}
}
public void pop() {
stack.pop();
minval.pop();
}
public int top() {
return stack.peek();
}
public int min() {
return minval.peek();
}
}
//优化空间 只有最小值的时候最小栈才出栈
import java.util.Stack;
public class Solution {
Stack<Integer> stack = new Stack<>();
Stack<Integer> minval = new Stack<>();
public void push(int node) {
stack.push(node);
if (minval.isEmpty()){
minval.push(node);
}
else{
if(node<=minval.peek()){
minval.push(node);
}
}
}
public void pop() {
if(stack.peek()<=minval.peek()){
minval.pop();
}
stack.pop();
}
public int top() {
return stack.peek();
}
public int min() {
return minval.peek();
}
}
27.二叉搜索树的后序遍历序列
解题思路:
首先树一般使用递归
判断是否是二叉树,首先递归,1你的左子树是二叉树 你的右子树是二叉树
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sw7HFdDy-1618561745433)(image-20210207191826759.png)]
然后退出的条件就是根据后序遍历的特点来了
后序遍历 根节点在后面 左子树部分都在前面右子树都在后面
1、首先传入的是空的话直接是false
2、找到左子树的部分后如果后面有小于根的部分那么就是错的
4、直到左右子树都为空(后序遍历的数组为空)就是true
import java.util.*;
public class Solution {
public boolean VerifySquenceOfBST(int [] sequence) {
if(sequence.length==0)return false;
return isBts (sequence);
}
public boolean isBts(int [] sequence){
if(sequence.length==0)return true;
int l = -1;
int m = sequence.length-1;
for(int i=0;i<m;i++){
if(sequence[i]>sequence[m]&&l==-1)
l=i;
if(sequence[i]<sequence[m]&&l!=-1)
return false;
}
if(l==-1)return true;
return isBts(Arrays.copyOfRange(sequence,0,l))
&&isBts(Arrays.copyOfRange(sequence,l,m));
}
}
28.-二叉树中和为某一值的路径
这题是典型的的二叉树深度遍历的题,我的经验就是树就是递归,这题就和求高度是一模一样的,难点在于路径队列的保存,我们都知道java是引用传递而不是值传递,保存后,在改变以前保存的也会改变,那么2步来解决:
1.找到后new一个队列ArrayList<Integer> s = new ArrayList<Integer>(result);
2每次返回上一层的时候删除队列中当前访问节点的值 result.remove(result.size()-1);//删除最后一个位置的数值
On On
import java.util.*;
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
ArrayList<Integer> result = new ArrayList<Integer>();
ArrayList<ArrayList<Integer>> results = new ArrayList<ArrayList<Integer>>();
dfs(root,target,0,result,results);
return results;
}
public void dfs(TreeNode root,int target,int tmp,ArrayList<Integer> result
, ArrayList<ArrayList<Integer>> results){
if(root==null )return;
result.add(root.val);
tmp+=root.val;
if(tmp==target&&root.left==null&&root.right==null){
ArrayList<Integer> s = new ArrayList<Integer>(result);
results.add(s);
}
dfs(root.left,target,tmp,result,results);
dfs(root.right,target,tmp,result,results);
result.remove(result.size()-1);//删除最后一个位置的数值
}
}
28.复杂链表的复制
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
解题思路: 分析题目,这是一个复杂链表,且要实现深拷贝,就是每一个节点都要自己new,问题就是随机节点新建后,必须存起来,等next范文到的时候不能新new,因为是同一个. 所以我的做法是将new过的节点放进map中,遇到new过的就拿出来,否则就新new.
我的做法代码比较繁琐,看比较简单的做法是先遍历一遍,将所有的节点和新的节点放进map,接着继续遍历一遍,将map中的拿出来即可!!
On On
/*
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
RandomListNode(int label) {
this.label = label;
}
}
*/
import java.util.*;
public class Solution {
public RandomListNode Clone(RandomListNode pHead)
{
HashMap<RandomListNode,RandomListNode> randoms = new HashMap<>();
if (pHead == null) return null;
RandomListNode head = new RandomListNode(pHead.label);
RandomListNode tmp = head;
while(pHead!=null){
//处理随机节点
if(pHead.random==null){
tmp.random=null;
}else{
//先判断随机结点是否已经拷贝
if(randoms.containsKey(pHead.random)){
tmp.random=randoms.get(pHead.random);
}else{
tmp.random = new RandomListNode(pHead.random.label);
randoms.put(pHead.random,tmp.random);
}
}
//处理下一个节点
if(pHead.next==null){
tmp.next=null;
}else{
if(randoms.containsKey(pHead.next)){
tmp.next=randoms.get(pHead.next);
}else{
tmp.next = new RandomListNode(pHead.next.label);
randoms.put(pHead.next,tmp.next);
}}
tmp = tmp.next;
pHead = pHead.next;
}
return head;
}
}
//简单做法
import java.util.HashMap;
public class Solution {
public RandomListNode Clone(RandomListNode pHead) {
if (pHead == null) return null;
// target 作为将要返回的头,记住要new的
RandomListNode target = new RandomListNode(pHead.label);
// cur 获取链表头
RandomListNode cur = pHead;
// p 获取新链表头
RandomListNode p = target;
HashMap<RandomListNode, RandomListNode> map = new HashMap<>();
// 由pHead将所有值存入map,每一个结点都要new的
while (pHead != null) {
map.put(pHead, new RandomListNode(pHead.label));
pHead = pHead.next;
}
// target作为新链表的头,由cur,p移动来复制链表
while (cur != null) {
p.next = map.get( cur.next );
p.random = map.get( cur.random );
cur = cur.next;
p = p.next;
}
return target;
}
}
28.字符串的排列
真的惡心的題目
遞歸回溯 也是動態規劃 用set
可以进行去重,并且可以达到按字母顺序排序。
时间复杂度:O(n!),比如3个字符的全n排列有6种 空间复杂度:O(n),原地交换 set
import java.util.*;
public class Solution {
public ArrayList<String> Permutation(String str) {
//递归法 递归退出 字符只剩一个
//递归体 固定字符串中的每一个字符到第一个位置 后面的字符串继续递归
ArrayList<String> result = new ArrayList<>();
if(str.length()>0&&str!=null){
dfs(str.toCharArray(),0,result);
//用这个方法需要排序
Collections.sort(result);
}
return result;
}
private void dfs(char []str,int index,ArrayList<String> result){
//退出递归
if(index == str.length-1)
result.add(String.valueOf(str));
else{
HashSet<Character> charSet = new HashSet<>();
for(int i = index;i<str.length;i++){
if(i == index || !charSet.contains(str[i])){
charSet.add(str[i]);
swag(str,index,i);
dfs(str,index+1,result);
swag(str,i,index);
}
}
}
}
private void swag(char []str,int i,int j){
char tmp = str[i];
str[i] = str[j];
str[j] = tmp;
}
}
我也是無語了,這樣寫就超時,我還是用回溯法吧
/**
* 非递归法 : 采用字典枚举法,例如123456 显然为字典的第一个 654321 就是最后一个
* 有规律
* 一个全排列可看做一个字符串,字符串可有前缀、后缀。
* 生成给定全排列的下一个排列.所谓一个的下一个就是这一个与下一个之间没有其他的。
* 这就要求这一个与下一个有尽可能长的共同前缀,也即变化限制在尽可能短的后缀上。
* <p>
* [例]839647521是1--9的排列。1—9的排列最前面的是123456789,最后面的987654321,
* 从右向左扫描若都是增的,就到了987654321,也就没有下一个了。否则找出第一次出现下降的位置。
* <p>
* 【例】 如何得到346987521的下一个
* 1,从尾部往前找第一个P(i-1) < P(i)的位置
* 3 4 6 <- 9 <- 8 <- 7 <- 5 <- 2 <- 1
* 最终找到6是第一个变小的数字,记录下6的位置i-1
* <p>
* 2,从i位置往后找到最后一个大于6的数
* 3 4 6 -> 9 -> 8 -> 7 5 2 1
* 最终找到7的位置,记录位置为m
* <p>
* 3,交换位置i-1和m的值
* 3 4 7 9 8 6 5 2 1
* 4,倒序i位置后的所有数据
* 3 4 7 1 2 5 6 8 9
* 则347125689为346987521的下一个排列
*/
import org.junit.jupiter.api.Test;
import java.util.*;
public class Solution {
@Test
public void main() {
System.out.println(System.currentTimeMillis());
System.out.println(this.Permutation("abcdefgsss"));
System.out.println(System.currentTimeMillis());
}
public ArrayList<String> Permutation(String str) {
ArrayList<String> list = new ArrayList<String>();
if(str.length()==0||str==""){
return list;}
//先找到第一个排列
char[] tmp = str.toCharArray();
Arrays.sort(tmp);
list.add(String.valueOf(tmp));
int length = tmp.length-1;
//找下一个
while (true) {
int cur = length;
int pre = cur - 1;
while (pre > -1&&tmp[cur]<=tmp[pre]) {
cur--;
pre--;
}
//找到最後一個的標志
if(pre == -1) break;
int j = length;
while(tmp[j]<tmp[pre]){
j--;
}
swag(tmp,pre, j);
reverse(tmp,pre);
list.add(new String(tmp));
}
return list;
}
private void reverse(char[] tmp, int pre) {
for(int j = 0;j<tmp.length/2;j++){
int n = pre+1+j;
int m = tmp.length-1-j;
if(n<m)
swag(tmp,n,m);
}
}
private void swag(char[] tmp, int i, int j) {
char charTmp = tmp[j];
tmp[j] = tmp[i];
tmp[i] = charTmp;
}
}
29.求给定数组的topK小问题。
就是排序问题 复习一下 这题大根堆和快排好点 我思路是冒泡
import java.util.*;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> resullt = new ArrayList<Integer>();
if(k==0||k>input.length)return resullt;
for(int i=0;i<k;i++){
for(int j =input.length-1;j>i;j--){
if(input[j]<input[j-1]){
int tmp = input[j];
input[j] = input[j-1];
input[j-1] = tmp;
}
}
resullt.add(input[i]);
}
return resullt;
}
}
30.把数组排成最小的数
时间复杂度Onlogn(明天看一下排序的源码) 空间复杂度 On
import java.util.*;
//方法一 暴力法 全排列然后比较大小
//方法二 重写compare方法 使用排序 两个字符串大小的比较按照 两种比较方法(我觉得好用的就是比较两个字符串s1, s2大小的时候,先将它们拼接起来,比较s1+s2,和s2+s1那个大,如果s1+s2大,那说明s2应该放前面,所以按这个规则,s2就应该排在s1前面。 ,突然大悟。)
//另一个是 例如 321 和 3 就是321 和 3 每个比 321 33 2<3所以 321<33
public class Solution {
public String PrintMinNumber(int [] numbers) {
String result="";
ArrayList<Integer> list=new ArrayList<>();
for (int num:numbers) list.add(num);
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
String s1=o1+""+o2;
String s2=o2+""+o1;
return s1.compareTo(s2);
}
});
for (int i:list) result+=i;
return result;
}
}
31.剑指offer-33-丑数
把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
2x*3y*5^z
所以只需要把得到的丑数不断地乘以2、3、5之后并放入他们应该放置的位置即可,
public class Solution {
public int GetUglyNumber_Solution(int index) {
if(index <= 0)return 0;
//思路 一个队列存放从小到大的每个丑数,将队列中的每个丑数都*2、3、5,就能获得所有的丑数,
int[] result = new int [index];
int p2 = 0,p3 = 0,p5 = 0;
result[0] = 1;
for(int i =1;i<index;i++ ){
//每次将2 3 5 中的最小值放进去,保证所有的丑数都在result中
result[i] = Math.min(result[p2]*2, Math.min(result[p3]*3, result[p5]*5));
// 保证丑数中每个位置 *2 3 5 均不在result中,相等的话就代表已经处理过了,可以再前进一步*235
if(result[i] == result[p2]*2) p2++;
if(result[i] == result[p3]*3) p3++;
if(result[i] == result[p5]*5) p5++;
}
return result[index-1];
}
}
如果遍历的话是不现实的,前面无所谓,后面的显然会越来愈大
32.翻转单词顺序列
On On 很牛的写法 相当于翻转两次
public class Solution {
public String ReverseSentence(String str) {
if(str.length() == 0 || str == null){
return "";
}
char[] c = str.toCharArray();
for(int i = 0, l = 0; i <= c.length; i++){
if(i == c.length || c[i] == ' ' ){
reverse(c, l, i - 1);//单词翻转
l = i + 1;//单词的初始位置
}
}
reverse(c, 0, c.length - 1);//排序翻转
return new String (c);
}
private void reverse(char [] c, int l, int h){
while(l < h){
swap(c, l++, h--);
}
}
private void swap(char [] c, int l, int h){
char t = c[l];
c[l] = c[h];
c[h] = t;
}
}
@Override
public int compare(Integer o1, Integer o2) {
String s1=o1+""+o2;
String s2=o2+""+o1;
return s1.compareTo(s2);
}
});
for (int i:list) result+=i;
return result;
}
}
### 31.剑指offer-33-丑数
> 把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
>
> 2^x*3^y*5^z
所以**只需要把得到的丑数不断地乘以2、3、5之后并放入他们应该放置的位置即可**,
```java
public class Solution {
public int GetUglyNumber_Solution(int index) {
if(index <= 0)return 0;
//思路 一个队列存放从小到大的每个丑数,将队列中的每个丑数都*2、3、5,就能获得所有的丑数,
int[] result = new int [index];
int p2 = 0,p3 = 0,p5 = 0;
result[0] = 1;
for(int i =1;i<index;i++ ){
//每次将2 3 5 中的最小值放进去,保证所有的丑数都在result中
result[i] = Math.min(result[p2]*2, Math.min(result[p3]*3, result[p5]*5));
// 保证丑数中每个位置 *2 3 5 均不在result中,相等的话就代表已经处理过了,可以再前进一步*235
if(result[i] == result[p2]*2) p2++;
if(result[i] == result[p3]*3) p3++;
if(result[i] == result[p5]*5) p5++;
}
return result[index-1];
}
}
如果遍历的话是不现实的,前面无所谓,后面的显然会越来愈大
32.翻转单词顺序列
On On 很牛的写法 相当于翻转两次
public class Solution {
public String ReverseSentence(String str) {
if(str.length() == 0 || str == null){
return "";
}
char[] c = str.toCharArray();
for(int i = 0, l = 0; i <= c.length; i++){
if(i == c.length || c[i] == ' ' ){
reverse(c, l, i - 1);//单词翻转
l = i + 1;//单词的初始位置
}
}
reverse(c, 0, c.length - 1);//排序翻转
return new String (c);
}
private void reverse(char [] c, int l, int h){
while(l < h){
swap(c, l++, h--);
}
}
private void swap(char [] c, int l, int h){
char t = c[l];
c[l] = c[h];
c[h] = t;
}
}