11.[二进制中1的个数]
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
方法一
1.转成二进制数
2.拿去和1相与
难点在于如何转换成二进制(这个复杂的方法可以锻炼一下能力)
方法二.
巧妙地相与
12.数值的整数次方
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。保证base和exponent不同时为0
13.调整数组顺序使得奇数在偶数面前
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
思路1: 从头开始,找第一个偶数和与它最近的一个奇数,把中间的偶数都向后平移一位,奇数插入偶数原来的位置,类似于插入排序
import java.util.ArrayList;
public class Solution {
public void reOrderArray(int [] array) {
if(array.length==0&&array==null)
return;
int i=0; int j;
while(i<array.length){
while(i<array.length&&!isEven(array[i])){//是奇数就往前
i++;
}
j=i+1;//第一个偶数
while(j<array.length&&isEven(array[j])){//是偶数就往前
j++;
}
if(j<array.length){//找到的奇数
int temp=array[j];
for(int k=j;k>i;k--){//所有偶数向前挪动
array[k]=array[k-1];
}
array[i++]=temp;
}else
break;
}
}
public boolean isEven(int n){
if(n%2==0){
return true;
}else
return false;
}
}
思路2: 一个指针ep从前往后走,找偶数,找到就停,另一个指针op从后往前走,找奇数,找到也停下来,当ep在op左边时,交换两个数,ep在op右边时,最右边的奇数在最左边偶数的左边,符合条件了,可以退出循环.。不可以这样做,应该题目要求保证奇数和奇数,偶数和偶数之间的相对位置不变。
14.链表中倒数第k个节点
输入一个链表,输出该链表中倒数第k个结点。
思路1: 利用栈
import java.util.Stack;
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
//1.use a stack,push all the element into the stack then pop the no.k element
if(head==null||k<1){
return null;
}
Stack s=new Stack();
while(head!=null){
s.push(head);
head=head.next;
}
if(k<=s.size()){
for(int i=0;i<k-1;i++){
s.pop();
}
return (ListNode)s.pop();
}else
return null;
}
}
思路2:两个指针相隔k步,前指针到尾部时,头指针就是第k个
import java.util.Stack;
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
//1.use a stack,push all the element into the stack then pop the no.k element
if(head==null||k<1){
return null;
}
ListNode h =head;
ListNode t = head;
int step =0 ;
while(step<k){
if(t==null){
return null;
}
t=t.next;
step++;
}
while(t!=null){//两个指针同步往前走
t = t.next;
h = h.next;
}
return h;
}
}
15.反转链表
输入一个链表,反转链表后,输出新链表的表头。
思路:三个指针,从头开始把指向反转
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode ReverseList(ListNode head) {
if(head==null)
return null;
if(head.next==null){
return head;
}
else{
ListNode pre,cur,next;
pre=null;
cur=head;
next=null;
while(cur!=null){
next=cur.next;
cur.next=pre;//反转
//移动
pre=cur;
cur=next;
}
return pre;
}
}
}
16.合并两个排序链表
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
思路:
非递归方法:先做鲁棒性检查,然后开始定head,head指向较小值的链表表头,然后让这个被指向的链表表头往后移动(相当于新表头了),再继续比较,当一个表空了就停止,把另一个表的接到队尾。
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1==null){
return list2;
}
if(list2==null)
return list1;
ListNode head = null;
ListNode cur =null;
while(list1!=null&&list2!=null){
if(list1.val<=list2.val)//如果A的值比较小
{
if(head==null){
head =cur =list1;
}else{
cur.next = list1;
cur=cur.next;
}
list1 = list1.next;
}else{
if(head==null){
head =cur =list2;
}else{
cur.next = list2;
cur=cur.next;
}
list2 = list2.next;
}
}
if(list1==null){
cur.next = list2;
}
if(list2 == null){
cur.next = list1;
}
return head;
}
}
自己做不好的点:
1.最后的连接多出来的部分做得太复杂
if(curA==null&&curB!=null)//如果list1先结束
{
while(curB!=null)
{
curAll.next = curB;
curB = curB.next;
}
}else if (curA!=null&&curB==null)
{
while(curA!=null)
{
curAll.next = curA;
curA = curA.next;
}
}
这样做是因为没有意识到,链表只要指向头节点就可以把后面的都串起来了,不用一个一个地去连接
2.一开始并没有想好head和cur节点之间应该如何赋值,其实加一个head为空的判断,如果是空的就head = cur,否则不需要这个,做连接操作就行,注意,head = cur只需要一次就能识别,就相当于cur一直往后连接节点,但head一直等于最初的cur,不需要head.next = cur.
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1==null){
return list2;
}
if(list2==null)
return list1;
ListNode head = null;
ListNode cur =null;
//找head最初应该指向的节点
if(list1.val<=list2.val)//如果A的值比较小
{ //将head指向list1
head = cur = list1;
list1 = list1.next;
}else//否则,A的值比较大
{ //将head指向list2
head = cur = list2;
list2 = list2.next;
}
//当list1,list2中元素不为空时
while(list1!=null&&list2!=null){
if(list1.val<=list2.val)
{//如果A的值比较小
cur.next = list1;
cur=cur.next;
list1 = list1.next;
}else
{
cur.next = list2;
cur=cur.next;
list2 = list2.next;
}
}
if(list1==null){
cur.next = list2;
}
if(list2 == null){
cur.next = list1;
}
return head;
}
}
3.我用curA,curB分别记录list1和list2中当前节点,其实没有必要,直接用list1,和list2就行了,因为这两个链表的头节点没有保存的必要,可以直接用来各自的cur节点
4.鲁棒性检查有错误,我一开始的写法
if(list1==null&&list2==null){
return null;
}
if(list1==null){
return list2;
}
if(list2==null)
return list1;
当只有一个是空的的时候也会返回null,就算错误的(写到这里觉得直接真的好傻逼,这种错误都犯!!!),而这个例子中的写法可以说很简洁优雅了。
递归版本:
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1==null){
return list2;
}
if(list2==null)
return list1;
ListNode head = null;
if(list1.val<=list2.val)//如果A的值比较小
{
head= list1;
head.next = Merge(list1.next,list2);
}else
{
head = list2;
head.next = Merge(list1,list2.next);
}
return head;
}
}
自己心里明白,如果不看讨论区,自己是写不出递归版本的,因为我没有意识到,连接一个链表不需要将链表里面的节点都连接,连接头节点就可以连接整个链表。
17.树的子结构
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
思路: 递归的思想,分为以下情况
1.如果两者有一个是空树,返回false。
2.对比A,B的根节点,如果值相等,那就分别对比左孩子和右孩子是不是一样
3.如果A,B的根节点值不相等,如果左子数不空,就看这颗树是否是左子树的子树,如果右子数不空,判断这颗树是否是右子树的子数
21.旋转打印数组
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,1
方法1:
分析:四个循环来打印,标记好边界坐标使思路清晰。对于只有一行和一列的特殊情况,需要在下边右到左以及左边下到上的时候增加条件判断。
示例代码:
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> printMatrix(int [][] matrix) {
//先获得数组长度宽度
int m = matrix[0].length;//第一行的长度就是长,横着数
int n = matrix.length;//有几行,就是竖着数
//结果保存到ArrayList里面
ArrayList<Integer> result = new ArrayList<Integer>();
//int currentLength = n;
//if(n ==1){
//result.add(matrix[0][0]);
//}else{
//一圈一圈地打印,一共打印n/2圈
//每次打印的开始是对角线上的第i的个点,也就是matrix[i,i]
int left = 0,right = m-1, top = 0,bottom = n-1;
while(left<=right&&top<=bottom){
for(int j1 = left;j1<=right;j1++)//1.第i圈从左往右,到底
{
result.add(matrix[top][j1]);
}
for(int j2 = top+1;j2<=bottom-1;j2++){//第i圈从上往下,不到底
result.add(matrix[j2][right]);
}
if(top!=bottom){
for(int j3 = right;j3>=left;j3--){//第i圈从右往左,到底
result.add(matrix[bottom][j3]);
}
}
if(left!=right){
for(int j4 = bottom-1;j4>top;j4--){//第i圈从下往上
result.add(matrix[j4][left]);
}
}
left++;
right--;
top++;
bottom--;
}
//}
return result;
}
}
一直编译不通过的原因:
1.最后两个循环的条件判断搞反了 ,在第三个循环之前应该是要判断是否够高,所以应该是top和bottom的关系,第四个是是否够宽,是left和right的关系
22.栈的最小值
定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数
(时间复杂度应为O(1))。
思路: 一个栈用来装数据s,另一个栈sMin用来保存当前状态下最小元素,比如,sMin的第i个元素,保存栈s的第i个元素进栈的时候,当前栈的最小值
import java.util.Stack;
public class Solution {
Stack s = new Stack();//用于操作的栈
Stack sMin = new Stack();//用于保存当前操作的栈中最小元素的值
int min;
public void push(int node) {
if(s.isEmpty()){
min = node;
}else if(node<min) {//进栈元素比较小,node进min栈,否则
min = node;
}
sMin.push(min);
s.push(node);
}
public void pop() {
s.pop();
sMin.pop();
}
public int top() {
if(s.isEmpty()){
return -1;
}else{
return (int)s.peek();
}
}
public int min() {
if(s.isEmpty()){
return -1;
}else {
return (int)sMin.peek();
}
}
}
注意:入栈出栈操作都会影响sMin栈
总结栈的方法:
方法 | 作用 |
---|---|
void push(Object obj) | 入栈 |
Object pop() | 出栈当前元素,如果为空则返回-1 |
Object peek() | 查看堆栈顶部的对象,但不从堆栈中移除它 |
isEmpty() | 判断栈是否为空 |
23.[栈的出栈,入栈]
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
思路:动态地模拟进栈出栈操作,新建一个空栈用来对比,从pushA里一个一个进栈,当栈不空而且出栈序列popA还有元素的时候,比较栈顶是否等于popA现在的元素,等于的话就出栈,并且popA往下移一个元素,不等的话就break;
当pushA的元素都全部进栈,如果栈不空则说明不符合,栈空才说明所有的出栈队列元素都匹配完而且出栈了。
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
public boolean IsPopOrder(int [] pushA,int [] popA) {
//新建一个栈模拟压入的序列
Stack sIn = new Stack();
//nextIn表示下一个要入栈的元素nextOut表示下一个要比较的出栈元素
int nextIn = 0;
int nextOut = 0;
int len = pushA.length;
while(nextIn<len)
{//当没有入栈完毕
sIn.push(pushA[nextIn]);//入栈
nextIn++;
while(nextOut<len&&(!sIn.isEmpty()))
{//当出栈序列没走完&栈不空
if((int)sIn.peek()==popA[nextOut])
{//栈定元素等于出栈序列当前元素
sIn.pop();//出栈当前元素
nextOut++;//出栈序列下标向后移
}else{break;}
}
}
return sIn.isEmpty();
}
}
一开始卡住的原因:我把序列先装进栈里面才开始一个一个弹出来比较,这个不科学,应该是个模拟的操作。
后来出现的问题:注意break语句,如果没有的话,会进入死循环。以后所有的只要if语句没有else语句的情况下都需要注意避免死循环的发生
24.从上往下打印二叉树
从上往下打印出二叉树的每个节点,同层节点从左至右打印。
思路: 用队列来实现,根进队然后循环:队列不空的时候,当前节点Val加入结果result列表–>左孩子不空则进队–>右孩子不空则进队
import java.util.ArrayList;
import java.util.Queue;
import java.util.LinkedList;
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
ArrayList<Integer> result = new ArrayList<Integer>();
TreeNode current;
if(root == null){
return result;
}else{
Queue<TreeNode> q = new LinkedList<TreeNode>();
q.add(root);
while(q.peek()!=null){
current = q.poll();//获取并移除队头
result.add(current.val);
if(current.left!= null){
q.add(current.left);
}if(current.right!= null){
q.add(current.right);
}
}
}
return result;
}
}
值得注意的是队列的构造,JDK中,LinkedList类实现了Queue接口,可以当Queue使用,所以我们是这样构造的:Queue q = new LinkedList();
错误用法:Queue q = new Queue();
总结队列的使用方法:
1.构造方法
方法 | 解释 |
---|---|
Queue q = new LinkedList() | LinkedList是实现了Queue接口的,可以用它来实现 |
2.操作方法
方法 | 解释 |
---|---|
add(E e) | 增加元素,无法添加节时抛出异常 |
offer(E e) | 增加元素,无法添加节时抛出异常,但是在有容量限制的时候会比add性能好 |
remove() | 获取并删除队头 |
poll() | 获取并删除队头,如果为空则返回null |
element() | 获取但不删除队头 |
peek() | 获取但不删除队头,如果为空则返回null |
25.二叉搜索树的后序遍历序列
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
思路: 二叉搜索树的后续遍历序列的特点就是根节点在最后,左子树遍历序列在右子树遍历序列的前面,左子树的元素值都比根节点小,右子树的元素值都比根节点大,可以利用这个特点去检查是否是二叉搜索树
public class Solution {
public boolean VerifySquenceOfBST(int [] sequence) {
//左子树的节点只都小于根节点,右子树的节点只都大于根节点,只需判断完成即可
//字符串是length字段,数组是方法
if(sequence==null||sequence.length==0)
{
return false;//判断
}else {
boolean isVerify = verify(0,sequence.length-1,sequence);
return isVerify;
}
}
//传入数组的开始和结束位置,代表子树的后序序列
public boolean verify(int start,int end,int [] sequence)
{
int lstart = start;//左子树序列开始位置
int lend =0; //左子树序列结束位置
int rstart =0;//右子树序列开始位置
int rend = end-1;//右子树序列结束位置
//树只有一个元素的时候,说明这个子树只有自己,左右子树都为空,返回是true,如果没有元素,放在后面处理
if(end<start){
return true;
}
if(end==start){
return true;
}
//end是根节点,先获取其值
int rootValue =sequence[end];
//开始找子树,找到之后判断大小并递归
while(rstart<end){
//找到第一个比根节点大的值,记录下来
if(sequence[rstart]>rootValue)
{
break;
}else{
rstart++;
}
}
lend = rstart-1;
//先检查右子树的情况,如果为空则只需要检查左子树
if(rstart ==rend){
//如果右子树是空的,但树的节点是一个以上,所以左子树不可能为空
return verify(lstart,lend,sequence);//对左树进行验证
}else{//右子树不空,需要检查左空或者左不空
//先验证右子树是否符合要求
for(int i = rstart;i<rend;i++)
{
if(sequence[i]<rootValue)//发现有小的,就返回false
{
return false;
}
}
//检查完大小再检查结构
//左空
if(rstart == lstart){
return verify(rstart,rend,sequence);
}else if(rstart<lstart){//左不空,返回两个
return verify(lstart,lend,sequence)&verify(rstart,rend,sequence);
}
}
}
}
一开始没有加这一段,一直通过不了
if(end<start){
return true;
}
我认为我代码不会出现到这一个end<start的情况,因为我后面之所以写得比别人复杂就是因为我想分清楚每一种情况,但是我相继检查到了几个我没有分得完全清楚导致end<start的错误
2处错误:
首先是while循环的边界,如果是rstart<end
的话,当右子树为空的时候,最后一次循环是rstart=end-1,t循环结束之后rstart==end的,而rend被设置为end-1,之后的循环中又没有处理rend<rstart的分支,这就是错的了
然后看lend = rstart -1
,如果左子树为空,rstart是第一个元素,lend-1就越数组下界了
这些都改了之后:
运行还是会有问题,而且还会发生数组越界的错误提示
后来仔细想了一下,发现lend和rstart最初不应该是从0开始的,应该是从start开始的
把那些0改正了之后发现运行可以通过了???