1、二维数组的查找
题目描述
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
思路
思路1:
暴力解--遍历整个数组,时间复杂度为O(n*n)
思路2:对每一行来说,都是单调递增的数组,利用二分法,查找每一行,时间复杂度为O(n*logn)
public boolean Find(int target, int [][] array) {
for(int i = 0; i < array.length; i++){
int low = 0;
int high = array[i].length - 1;
while(low <= high){
int mid = low + (high - low) >> 1;
if(target > array[i][mid])
low = mid + 1;
else if(target < array[i][mid])
high = mid - 1;
else
return true;
}
}
return false;
}
思路3:考虑数组从左到右递增,从上到下递增,比较target与右上或左下的值 array[row][col]. target >array[row][col],表示target处在当前元素所在列的下边;target <array[row][col],表示target处在当前元素所在行的左边;
public boolean Find(int target, int [][] array) {
int row = 0;
int col = array[0].length - 1;
while(row <= array.length - 1 && col >= 0){
if(target == array[row][col])
reurn true;
else if(target > array[row][col])
row++;
else
col--;
}
}
2、替换空格
请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
思路1:
新建一个string,若要方便添加元素,则创建一个StringBuilder.。遇到空格添加三个字符‘%’,‘2’,‘0’。
public String replaceSpace(StringBuffer str) {
if(str == null)
return null;
StringBuilder newstr = new StringBuilder();
for(int i = 0; i < str.length(); i++){
if(str.charAt(i) == ' '){
newstr.append('%');
newstr.append('2');
newstr.append('0');
}else
newstr.append(str.charAt(i));
}
return newstr;
}
思路2:
在原字符串上重新建立新串。首先需要计算新串的长度,然后按倒序的将字符加进新串。
public String replaceSpace(StringBuffer str) {
if(str == null)
return null;
//计算空格的个数
int spaceNum = 0;
for(int i = 0; i < str.length; i++){
if(str.charAt(i) == ' ')
spaceNum++;
}
//定义字符串的新长度
int len = str.length;
int newLen = len + spaceNum * 2;
str.setLength(newLen);
for(int i = len - 1; i >= 0; i--){
if(str.charAt(i) == ' '){
str.setCharAt(newLen--, '0');
str.setCharAt(newLen--, '2');
str.setCharAt(newLen--, '%');
}else{
str.setCharAt(newLen--, str.charAt(i));
}
}
return str.toString();
}
3、从尾到头打印链表
题目描述
输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。
思路
思路1:利用递归,一致低轨道链表结尾,在返回时加入数值。
ArrayList<Integer> list = new ArrayList<Integer>();
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
if(listNode != null){
ArrayList<Integer> printListFromTailToHead(listNode.next);
list.add(listNode.val);
}
}
思路2:利用栈,先将链表中的元素压进栈,然后一个一个出栈添加进链表。
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> a = new ArrayList<Integer>();
Stack stack = new Stack();
while(listNode != null){
stack.push(listNode);
listNode = listNode.next;
}
while(!stack.isEmpty()){
a.add(stack.pop());
}
return a;
}
思路3: 逆转链表,首先记住当前节点的next节点,然后将当前节点指向前一节点,然后更新当前节点为pre节点,next节点为当前节点。
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> list=new ArrayList<Integer>();
ListNode pre = null;
ListNode next = null;
while(listNode != null){
next = listNode.next;
listNode.next = pre;
pre = listNode;
listNode = next;
}
while(pre != null){
list.add(pre.val);
pre = pre.next;
}
return list;
}
4、重建二叉树
题目描述
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
思路
先序遍历第一个位置肯定是根节点node,
中序遍历的根节点位置在中间p,在p左边的肯定是node的左子树的中序数组,p右边的肯定是node的右子树的中序数组;
另一方面,先序遍历的第二个位置到p,也是node左子树的先序子数组,剩下p右边的就是node的右子树的先序子数组,把四个数组找出来,分左右递归调用即可。
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
if(pre.length != in.length || pre.length == 0 || in.length == 0)
return null;
//先序遍历第一个值肯定是头节点
TreeNode root = new TreeNode(pre[0]);
//在中序遍历中找到和头节点相同的值的位置,依此位置划分左右子树
int index = 0;
while(in[index] != root.val)
index++;
//定义四个左右子树数组
int[] preleft = new int[index + 1];
int[] inleft = new int[index + 1];
int[] preright = new int[pre.length - index - 1];
int[] inright = new int[in.length - index - 1];
for(int i = 0; i < in.length; i++){
if(i < index){
preleft = pre[i];
inleft = in[i];
}else{
preright[j - i - 1] = pre[j];
inright[j - i - 1] = in[j];
}
root.left = reConstructBinaryTree(preleft, inleft);
root.right = reConstructBinaryTree(preright, inright);
return root;
}
}
5、用两个栈实现队列
题目描述
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
思路
一个栈用来做push操作,一个栈用来做pop()操作。这时需要注意,只有当pop栈是空的时候,push栈才能往里压元素,非空的时候直接返回pop栈的最上面的一个节点。
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.isEmpty()){
return stack2.pop();
}
if(stack1 == null){
return 0;
}else{
while(!stack1.isEmpty()){
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
6、旋转数组的最小数字
题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
思路
思路1:直接遍历数组,找到第一个前面的数大于后面的数,返回后面的数即可。时间复杂度为O(n)。
public int minNumberInRotateArray(int [] array) {
int y=0;
for(int i=0;i<array.length;i++){
if(array[i]>array[i+1]){
y=array[i+1];
break;
}
}
return y;
}
}
思路2:使用二分查找,比较中间元素和两头的值的大小,确定在哪一部分。时间复杂度O(logn)。
第一种情形:array[mid] > array[hi] -- lo = mid + 1;
第二种情形:array[mid] == array[hi] -- 不能确定最小值是在mid左边还是右边,hi = hi - 1;
第三种情形:array[mid] < array[hi] -- hi = mid,只剩两个数的时候。mid会在lo的位置,所以返回array[lo];
public int minNumberInRotateArray(int [] array) {
//二分查找
if(array.length == 0)
return 0;
int lo = 0;
int hi = array.length - 1;
while(lo < hi){
int mid = lo + ((hi - lo) >> 1);
if(array[mid] > array[hi]){
lo = mid + 1;
}else if(array[mid] == array[hi]){
hi = hi - 1;
}else
hi = mid;
}
return array[lo];
}
7、斐波那契数列
题目描述
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。n<=39
思路
前两个数和是下一个数,为了保证时间复杂度为O(n)和空间复杂度为O(1)。采用非递归版本
public int Fibonacci(int n) { //非递归版本
if(n == 0 || n ==1)
return n;
int res = 0;
int pre = 1;
int prepre = 0;
for(int i = 1;i < n; i++){
res = prepre + pre;
prepre = pre;
pre = res;
}
return res;
}
public int Fibonacci(int n) { 非递归版本
if(n == 0 || n ==1)
return n;
if(n == 2)
return 1;
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
8、跳台阶
题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
思路
考虑target-1,有多少中方案到target-1,就有多少中方案到target;考虑target-2,有多少中方案到target-2,就有多少中方案到target;将以上两种情况加起来就是答案f(target)=f(target-1)+f(target-2),即是斐波那契数列
public int Fibonacci(int n) { //非递归版本
if(n == 0 || n ==1)
return n;
int res = 0;
int pre = 1;
int prepre = 0;
for(int i = 1;i < n; i++){
res = prepre + pre;
prepre = pre;
pre = res;
}
return res;
}
9、变态跳台阶
题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
思路
因为n级台阶,第一步有n种跳法:跳1级、跳2级、到跳n级。跳1级,剩下n-1级,则剩下跳法是f(n-1);跳2级,剩下n-2级,则剩下跳法是f(n-2);所以f(n)=f(n-1)+f(n-2)+...+f(1),因为f(n-1)=f(n-2)+f(n-3)+...+f(1), 所以f(n)=2*f(n-1)
public int JumpFloorII(int target) {
return (int)Math.pow(2,target - 1);
}
10、矩形覆盖
题目描述
我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
思路:这个问题依旧是斐波那契数列问题,考虑最边上要么竖着放一块,要么横着放2块,可以扩展到(1*m覆盖m*n区域)问题。
public int RectCover(int target) {
//归纳法--依旧是斐波那契数列
//如果用1*m的方块覆盖m*n区域,递推关系式为f(n) = f(n-1) + f(n-m),(n > m)
if(target < 1)
return 0;
if(target == 1 || target == 2)
return target;
int res = 2;
int pre = 1;
int tmp = 0;
for(int i = 3;i <= target;i++){
tmp = res;
res = res + pre;
pre = tmp;
}
return res;
}
11、二进制中1的个数
题目描述
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
思路
将整数转化为二进制字符串
public int NumberOf1(int n) {
int count=0;
String result=Integer.toBinaryString(n); //把整数转换成二进制数
for(int i=0;i<result.length();i++){
if(result.charAt(i)=='1')
count++;
}
return count;
}
12、数值的整数次方
题目描述
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
思路
考虑指数的正负
public double Power(double base, int exponent) {
double result=1.0;
if(base==0.0) return 0;
if(exponent>0)
for(int i=0;i<exponent;i++){
result=result*base;
}
if(exponent<0){
double basenew=1/base;
int exponentnew=-exponent;
for(int i=0;i<exponentnew;i++){
result=result*basenew;
}
}
if(exponent==0)
result=1;
return result;
}
13、调整数组顺序使奇数位于偶数前面
题目描述
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变
思路
思路1:判断奇数和偶数的条件是i % 2是否等于0,另开辟一个新的数组,奇数从前往后放,偶数从后往前放,遍历完数组后,将奇数放回按顺序放回原数组,偶数倒序放回。
思路2:类似冒泡算法,前偶后奇就交换
//思路1:这里可以改成遍历一回
public void reOrderArray(int [] array) {
int [] a=new int[array.length];
int count=0;
for(int i=0;i<array.length;i++){
if(array[i]%2==1){
a[count]=array[i];
count++;
}
}
for(int i=0;i<array.length;i++){
if(array[i]%2==0){
a[count]=array[i];
count++;
}
}
for(int i=0;i<array.length;i++){
array[i]=a[i];
}
}
//思路2
public void reOrderArray(int [] array) {
for(int i= 0;i<array.length-1;i++){
for(int j=0;j<array.length-1-i;j++){
if(array[j]%2==0&&array[j+1]%2==1){
int t = array[j];
array[j]=array[j+1];
array[j+1]=t;
}
}
}
}
//思路2另一种写法
public void reOrderArray(int [] array) {
//相对位置不变,稳定性
//插入排序的思想
int m = array.length;
int k = 0;//记录已经摆好位置的奇数的个数
for (int i = 0; i < m; i++) {
if (array[i] % 2 == 1) {
int j = i;
while (j > k) {//j >= k+1
int tmp = array[j];
array[j] = array[j-1];
array[j-1] = tmp;
j--;
}
k++;
}
}
}
14、链表中倒数第k个结点
题目描述
输入一个链表,输出该链表中倒数第k个结点。
思路
先遍历链表统计节点个数,然后计算从前往后是第几个,输出即可。
public ListNode FindKthToTail(ListNode head,int k) {
ListNode h=head;
if(h==null)
return null;
int count=1;
while(h.next!=null){
count++;
h=h.next;
}
int t=count-k+1;
ListNode h2=head;
for(int i=1;i<=count;i++){
if(i==t)
break;
h2=h2.next;
}
return h2;
}
栈的压入、弹出序列
题目描述
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
思路
使用栈,对于压入数据使用栈,需要判断弹出数据是否和栈顶数据相同,相同的话就弹出栈顶数据,同时判断弹出数据的下一个,否则则继续压入数据,直到压入数据用完。
public boolean IsPopOrder(int [] pushA,int [] popA) {
if(pushA == null || popA == null)
return false;
Stack<Integer> stack = new Stack<>();
int i = 0; //pushA的指针
int j = 0; //popA的指针
stack.push(pushA[i++]);
while(j < popA.length){
while(popA[i] != stack.peek()){
if(i == pushA.length)
return false;
stack.push(pushA[i++]);
}
j++;
stack.pop();
}
return stack.empty();
}
二叉搜索树与双向链表
题目描述
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
思路
二叉搜索树的特点是左子节点小于父节点,右子节点大于父节点。要想按照排序组成双向链表,只需要按照树的中序遍历的顺序调整节点之间的指针即可。
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
public TreeNode Convert(TreeNode root){
if(root == null)
return root;
Stack<TreeNode> stack = new Stack<>();
TreeNode head = root;
TreeNode pre = null;
boolean flag = true; //找到最左边的节点
while(root != null || !stack.isEmpty()){
while(root != null){ //找到最左的节点
stack.push(root);
root = root.left;
}
root = stack.pop();
if(flag){
head = root;
pre = head;
flag = false;
}else{
pre.right = root;
root.left = pre;
pre = root;
}
root = root.right;
}
return head;
}
字符串的排列
题目描述
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
输入描述:输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。
思路
字符串的每一个位置都要和其他位置进行交换
public ArrayList<String> Permutation(String str) {
ArrayList<String> list = new ArrayList<>();
if(str == null || str.length() == 0)
return list;
char[] s = str.toCharArray();
process(s, 0, list);
return list;
}
public void process(char[] str, int i, ArrayList<String> list){
//停止条件
if(i == str.length - 1){
if(!list.contains(String.valueof(str)))
list.add(String.valueof(str));
}
for(int j = i; j < str.length; j ++){
swap(str, i, j);
process(str, i + 1, list);
swap(str, i, j);
}
}
public void swap(char[] str, int i, int j){
char c = str[i];
str[i] = str[j];
str[j] = c;
}
数组中出现次数超过一半的数字
题目描述
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
思路
方法一:使用HashMap,统计各个数字出现的次数,然后遍历找到出现次数超过一半的数字。
方法二:利用哪个数字出现的次数超过一半的特点,先假设是第一个数,依次遍历后面的数和它比较,相等则它的数量加1,不等则减一,若是等于0,说明不是这个数,将要比较的数设为当前值,然后继续比较后面的数。最后再遍历整个数组确认一下是找到的那个值。
public int MoreThanHalfNum_Solution(int [] array) {
if(array.length < 1 || array == null)
return 0;
int target = array[0]; //先将目标定为第一个数
int count = 1; //第一个数的起始数量为1
for(int i = 1; i < array.length; i ++){
if(target != array[i]){
count--;
}else{
count++;
}
if(count == 0){
target = array[i];
count = 1;
}
}
//对目标进行确认
count = 0;
for(int i = 0; i < array.length; i ++){
if(target == array[i]){
++count;
}
if(count > array.length / 2)
return target;
}
return 0;
}
最小的K个数
题目描述
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
思路
方法一:直接使用排序
方法二:使用小根堆和大根堆
方法三:先假设前K个是最小的数,然后遍历剩下的数,如果比前K个数中的最大值小则进行替换;
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> list = new ArrayList<>();
if(input.length == 0 || k < 0 || k > input.length )
return list;
//先将前K个数保存在list中
for(int i = 0; i < k; i ++)
list.add(input[i]);
int tmp = 0;
for(int i = k; i < input.length; i ++){
tmp = getMaxOfList(list);
if(input[i] < list.get(tmp)){
list.set(tmp, input[i]);
}
}
return list;
}
public int getMaxOfList(ArrayList<Integer> list){
int max = Integer.MIN_VALUE;
int index = 0;
for(int i = 0; i < list.size(); i ++){
if(list.get(i) > max){
max = list.get(i);
index = i;
}
}
return index;
}
连续子数组的最大和
题目描述
HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)
思路
检查当前和加上下一个数和下一个数的大小(因为当前和可能是负数),得出较大值保存下来,最后找到较大值中最大的值。
public int FindGreatestSumOfSubArray(int[] array) {
if(array.length == 0 || array == null)
return 0;
int max = array[0];
int res = array[0];
for(int i = 1; i < array.length; i ++){
max = Math.max(max + array[i], array[i]);
res = Math.max(max, res);
}
return res;
}
整数中1出现的次数
题目描述
求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。
思路
分别考虑当前位(个十百千)1的个数,然后将各个位1的个数加起来。
以百位为例:可能为0,1,2-9
为0时:12013中百位为1的个数为100-199、1100-1199、2100-2199、... 、10100-10199、11100-11199,一共12*100个,受高位影响;
为1时:12113中百位为1的个数为100-199、1100-1199、2100-2199、... 、10100-10199、11100-11199,一共12*100个,除了这些,还有12100-12113,一共14 = 13 + 1,此时不仅受高位影响还受低位影响;
为2-9时:12213中百位为1的个数为100-199、1100-1199、2100-2199、... 、10100-10199、11100-11199、12100-12199,一共13*100个,此时受高位(高位+1)影响;
public int NumberOf1Between1AndN_Solution(int n){
if(n < 1)
return 0;
int count = 0; //计数
//需要定义三个变量:高位数、当前位数、低位数
int high = 0, cur = 0, low = 0;
//定义一个位数变量
int i = 1;
while(n / i > 0){
high = n / (i * 10);
cur = (n / i) % 10;
low = n - (n / i) * i;
if(cur == 0){
count += high * i;
}else if(cur == 1){
count += high * i + low + 1;
}else{
count += (high + 1) * i;
}
i = i * 10;
}
return count;
}
把数组排成最小的数
题目描述
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323
思路
实现一个比较器:对于字符串str1和字符串str2,str1 + str2 > str2 + str1,则让str1 + str2在前面
public String PrintMinNumber(int [] numbers) {
if(numbers == null || numbers.length < 1)
return "";
//先将数字转化为字符串格式
String[] str = new String[numbers.length];
for(int i = 0; i < numbers.length; i ++){
str[i] = String.valueof(numbers[i]);
}
return smallestNumber(str);
}
//定义一个比较器
public class myComparator implements Camparator<String>{
public int compare(String o1, String o2){
return (o1 + o2).compareTo(o2 + o1);
}
}
public String smallestNumber(String[] str){
if(str.length() == 0 || str == null)
return "";
Arrays.sort(str, new myComparator());
String res = "";
for(int i = 0; i < str.length; i ++){
res += str[i];
}
return res;
}
丑数
题目描述
把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
思路
一个丑数肯定是由另一个丑数乘以2或3或5得到。为保持有序性,建立一个丑数数组,维护三个队列,每个队列的队首表示此队列指针对应的丑数乘以2或3或5的结果,每次比较每个队列的队首,取最小的那一个放进丑数数组。直至找到结果。
public int GetUglyNumber_Solution(int index){
if(index < 0)
return 0;
int[] res = new int[index];
res[0] = 1; //指定第一个丑数
//定义三个指针
int i2 = 0;
int i3 = 0;
int i5 = 0;
int count = 0; //计数变量
while(count < index){
int tmp = min(res[i2] * 2, min(res[i3] * 3, res[i5] * 5)); //找出最小的值
//将最小值的索引加1
if(tmp == res[i2] * 2) i2++;
if(tmp == res[i3] * 3) i3++;
if(tmp == res[i5] * 5) i5++;
res[++count] = tmp; //保存结果
}
return res[index - 1];
}
public int min(int i, int j){
return i > j ? j : i;
}
第一个只出现一次的字符
题目描述
在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写)。
思路
按照字母的ASCII码值作hash来做数组的索引。A-Z的ASCII码为65-90,a-z对应的ASCII码值为97-122,建立一个大小为58的数组(122 - 65 + 1 = 58),来保存字母的频次,之后再依次遍历字符串个位置字符。
public int FirstNotRepeatingChar(String str) {
if(str.length() < 1 || str == null)
return -1;
int[] num = new int[58];
for(int i = 0; i < str.length(); i ++){
num[str.charAt(i) - 65] += 1;
}
for(int i = 0; i < str.length(); i ++){
if(num[str.charAt(i) - 65] == 1)
return i;
}
return -1;
}
数组中的逆序对
题目描述
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
思路
对于逆序对问题,借助归并排序,在归并排序过程中可以计算有多少逆序对。
public int InversePairs(int [] array) {
if(array.length == 0 || array == null)
return 0;
return mergeSort(array, 0, array.length - 1);
}
private int mergeSort(int[] array, int lo, int hi){
if(hi == lo)
return 0;
int mid = lo + ((hi - lo) >> 1);
return (mergeSort(array, lo, mid) + mergeSort(mid + 1, hi) + merge(array, lo, mid, hi)) % 1000000007;
}
private int merge(int[] array, int lo, int mid, int hi){ //归并是对每一部分(lo, hi)进行归并
//首先建立一个辅助数组以及一个属于辅助数组的指针
int[] help = new int[hi - lo + 1];
int i = 0;
//定义一个逆序对数的计数值
int p = 0;
//定义两个起始点
int p1 = lo;
int p2 = mid + 1;
while(p1 <= mid && p2 <= hi){
p += array[p1] > array[p2] ? mid - p1 + 1 : 0;
if(P > 1000000007)
P = P % 1000000007;
help[i++] = array[p1] > array[p2] ? array[p2++] : array[p1++];
}
while(p1 <= mid){
help[i++] = array[p1++];
}
while(p2 <= hi){
help[i++] = array[p2++];
}
for(int j = 0; j < help.length; j ++){
array[lo + j] = help[j];
}
return p;
}
两个链表的第一个公共结点
题目描述
输入两个链表,找出它们的第一个公共结点。
思路
方法一:使用HashMap,将链表1中的节点放入map中,然后比对链表2中的节点,输出第一个重复的节点。
方法二:采用双指针,一个指向一个链表。让两指针同时行动,当一个指针先到达尾节点时就将其指向另一个链表的表头,直到两指针节点相等。
方法3:先统计两链表长度,计算差值。先让一指针走差值的长度,然后另一链表指针开始走,直至相遇,输出相遇节点。
//方法二
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
if(pHead1 == null || pHead2 == null)
return null;
ListNode p1 = pHead1;
ListNode p2 = pHead2;
while(p1 != p2){
p1 = (p1 == null) ? pHead2 : p1.next;
p2 = (p2 == null) ? pHead1 : p2.next;
}
return p1;
}
数字在排序数组中出现的次数
题目描述
统计一个数字在排序数组中出现的次数。
思路
遍历数组,遇到相等的数计数值加1,如果计数值大于1,且当前数不等于该数字,直接break。
public int GetNumberOfK(int [] array , int k) {
if(array.length == 0 || array == null)
return 0;
int count = 0;
for(int i = 0; i < array.length; i ++){
if(array[i] == k)
count ++;
if(count > 0 && array[i] != k)
break;
}
return count;
}
二叉树的深度
题目描述
输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
思路
递归:分别收集左子树和右子树的高度,返回较大值。
非递归:采用层次遍历,使用队列,当从队列中弹出节点数量等于上一层加入队列的节点数时,则深度值加1,知道队列为空。
//递归
public int TreeDepth(TreeNode root) {
if(root == null)
return 0;
int leftDepth = TreeDepth(root.left);
int rightDepth = TreeDepth(root.right);
return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}
//非递归
public int TreeDepth(TreeNode root) {
if(root == null)
return 0;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int depth = 0; //深度
int count = 0; //每一层的计数
int nextCount = 1; //每层加入队列的节点个数,第一层是root节点,只有一个
while(!queue.isEmpty()){
root = queue.poll();
count++;
if(!root.left.isEmpty())
queue.offer(root.left);
if(!root.right.isEmpty())
queue.offer(root.right);
if(count == nextCount){ //一层已经计算完了
depth ++;
count = 0; //重新计数下一层
nextCount = queue.size();
}
}
return depth;
}
平衡二叉树
题目描述
输入一棵二叉树,判断该二叉树是否是平衡二叉树。
思路
收集左子树和右子树的高度,比较两者的大小是否超过1。
public boolean IsBalanced_Solution(TreeNode root) {
if(root == null)
return false;
boolean[] res = new boolean[1];
res[0] = true;
getHigth(root, 1, res);
return res[0];
}
boolean getHigth(TreeNode root, int level, boolean[] res){
if(root == null)
return level;
int lefthigh = getHigth(root.left, level + 1, res);
if(!res[0])
return level;
int righthigh = getHigth(root.right, level + 1, res);
if(!res[0])
return level;
if(Math.abs(lefthigh - righthigh) > 1)
res[0] = false;
return Math.max(lefthigh, righthigh);
}
和为S的连续正数序列
题目描述
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
思路
连续正数序列是一个公差为1的等差数列,序列的中间值代表平均值的大小。序列的长度为n,则均值为sum/n,由此可以求出序列。但需要考虑n为奇数和偶数的情况。
n为奇数:均值为中间数,条件满足:n & 1 == 1 && sum % n == 0
n为偶数:均值为中间两个数的均值,小数部分我为0.5,sum % n 代表一般的长度,则条件满足:(sum % n)* 2 = n
根据等差数列的求和公式:S = (1 + n) * n / 2,得到n的范围,由题意可知,n至少为2.
public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
ArrayList<ArrayList<Integer>> alist = new ArrayList<ArrayList<Integer> >();
for(int n = (int)Math.sqrt(2 * sum); n >= 2; n--){
if((n & 1) == 1 && (sum % n == 0) || (sum % n) * 2 == n){
ArrayList<Integer> blist =new ArrayList<Integer>();
for(int j = 0, k = ((sum / n) - (n - 1) / 2); j < n; j++, k++){
blist.add(k);
}
alist.add(blist);
}
}
return alist;
}
和为S的两个数字
题目描述
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
思路
考虑到数组递增有序,则需要求的两个数在两头找到的第一对乘积是最小的。
public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
ArrayList<Integer> list = new ArrayList<Integer>();
if(array.length < 2 || array == null)
return list;
int i = 0;
int j = array.length - 1;
while(i < j){
if(array[i] + array[j] == sum){
list.add(array[i]);
list.add(array[j]);
return list;
}else if(array[i] + array[j] > sum){
j--;
}else{
i++;
}
}
return list;
}
左旋转字符串
题目描述
汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!
思路
方法一:采用substring()直接截取,然后拼接;
方法二:一个一个移过去,实际是从前移到最后;
方法三:YX = (XTYT)T
//方法一
public String LeftRotateString(String str,int n) {
if(str.length() == 0)
return "";
if(n == 0)
return str;
String str1 = str.substring(0, n);
String str2 = str.substring(n, str.length());
return str2 + str1;
}
//方法二
public String LeftRotateString(String str,int n) {
if(str.length() == 0)
return "";
if(n == 0)
return str;
char[] s = str.toCharArray();
for(int i = 0; i < n; i ++){
for(int j = 0; j < s.length; j ++)
swap(s, j, j + 1);
}
return String.valueOf(s);
}
void swap(char[] s, int i, int j){
char tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
//方法三
public String LeftRotateString(String str,int n) {
char[] chars = str.toCharArray();
if (chars.length < n) {
return "";
}
reverse(chars, 0, n - 1);
reverse(chars, n, chars.length - 1);
reverse(chars, 0, chars.length - 1);
return String.valueOf(chars);
}
}
public void reverse(char[] chars, int start, int end) {
while (start < end) {
char temp = chars[start];
chars[start] = chars[end];
chars[end] = temp;
start++;
end--;
}
}
翻转单词顺序列
题目描述
牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?
思路
按空格划分成string数组,然后倒序相加,注意每加一个字符串需要加空格,最后一个不加。
public String ReverseSentence(String str) {
if(str == null)
return null;
if(str.trim().equals("")) //trim()--去掉空格
return str;
String[] s = str.split(" ");
String res = "";
for(int i = s.length - 1; i >= 0; i--){
res += s[i];
if(i > 0)
res += " ";
}
return res;
}
扑克牌顺子
题目描述
LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)...他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子.....LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。
思路
有序数之间的差值是否有足够的0来填补。
排序后统计0的个数,然后先找到第一个不为0的数,判断前后两个数之间的差是否大于0的个数+1;然后在0的个数大于1的情况下,判断前后两个值是否相等;然后减去已经发生替换的值。在0的个数为0的情况下,判断前后两个数之间差值是否大于1。
public boolean isContinuous(int [] numbers) {
if(numbers.length < 5 || numbers == null)
return false;
Arrays.sort(numbers);
int count = 0;
for(int i = 0; i < numbers.length; i ++){
if(numbers[i] == 0)
count++;
}
if(count == 4) return true;
int j = 0;
while(j < numbers.length - 1){
while(numbers[j] == 0) //先找到第一个不是0的值
j++;
if(count > 0 && numbers[j + 1] - numbers[j] - 1 > count) //如果两个有序数之间的差大于0的个数
return false;
else if(count > 0){
if(numbers[j + 1] == numbers[j]) //如果有两个数不为0且相等
return false;
count -= (numbers[j + 1] - numbers[j] - 1); //count减去已经替换的数
j++;
continue;
}
if(count == 0 && (numbers[j + 1] - numbers[j] > 1)) //在没有0的情况下有两个数之间的差值超过2
return false;
j++;
}
return true;
}
孩子们的游戏(圆圈中最后剩下的数)
题目描述
每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0...m-1报数....这样下去....直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)
思路
使用队列,在未到m-1时,让队列里的元素不停的出队并入队;到m-1时只让当前元素出队。
public int LastRemaining_Solution(int n, int m) {
if(n < 1 || m < 1)
return -1;
if(n == 1)
return 0;
Queue<Integer> queue = new LinkedList<>();
for(int i = 0; i < n - 1; i ++)
queue.offer(i);
int count = 0;
while(queue.size() != 1){
if(count != m - 1){
queue.offer(queue.poll());
count++;
}else{
queue.poll();
count = 0;
}
}
return queue.poll();
}
求1+2+3+...+n
题目描述
求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
思路
方法1:采用等差数列求和等式;
方法2:利用递归求和;
//方法1:采用等差数列求和等式
public int Sum_Solution(int n) {
int sum = (int)Math.pow(n, 2) + n;
return sum >> 1;
}
//方法2:利用递归求和
public int Sum_Solution(int n) {
int sum = n;
if(n == 0)
return sum;
return sum + Sum_Solution(n - 1);
}
不用加减乘除做加法
题目描述
写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
思路
采用位运算,分别计算个位和进位。
public int Add(int num1,int num2) {
while(num2 != 0){
int tmp = num1 ^ num2; //个位
num2 = (num1 & num2) << 1; //进位
tmp = num1;
}
return num1;
}
把字符串转换成整数
题目描述
将一个字符串转换成一个整数(实现Integer.valueOf(string)的功能,但是string不符合数字要求时返回0),要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0。
思路
首先将字符串转化为字符,并将相应字符通过ASCII码的形式转化为数字。略过第一个字符,判断当前字符是否是字母,是字母直接返回;不是则判断数字当前位数转化为十进制数相加。最后判断正负返回值。
public int StrToInt(String str) {
if(str.length() < 1 || str == null)
return 0;
char[] s = str.toCharArray();
int[] a = new int[s.length];
for(int i = 0; i < s.length; i++){
if(s[i]>= 48 && s[i] <= 57)
a[i] = (s[i] - 48);
}
int len = a.length - 1;
int sum = 0;
for(int i = 0; i < a.length; i++){
if(i == 0 && (s[i] == '-' || s[i] == '+')){
continue;
}
if((s[i] >= 'a' && s[i] <= 'z') || (s[i] >= 'A' && s[i] <= 'Z'))
return 0;
sum += a[i] *(int)Math.pow(10, len - i);
}
if(s[0] == '-')
sum = (-1) * sum;
return sum;
}
数组中重复的数字
题目描述
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
思路
直接记录出现数的次数,遇到第一个出现次数大于1的数记录下来,然后返回。
public boolean duplicate(int numbers[],int length,int [] duplication) {
int[] flag = new int[10];
for(int i = 0; i < length; i ++){
if(flag[numbers[i]] == 1){
duplication[0] = numbers[i];
break;
}else{
flag[numbers[i]]++;
}
}
return duplication[0] > 0 ? true : false;
}
按之字形顺序打印二叉树
题目描述
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
思路
方法1:使用LinkedList,在变换打印方向时控制弹出节点和入队节点的方向即可(头部或尾部);
方法2:按照层次遍历的方式,遇到需要变换方向打印的行,将此行调转顺序即可;
//方法1
public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer>> alist = new ArrayList<ArrayList<Integer>>();
if(pRoot == null)
return alist;
boolean flag = true; //控制之字形的方向
LinkedList<TreeNode> queue = new LinkedList<>();
queue.offer(pRoot);
int count = 0;
int nextCount = 1;
ArrayList<Integer> blist = new ArrayList<Integer>();
while(!queue.isEmpty()){
if(flag){ //打印方向变化时只需调整弹出节点和入队节点的方向即可(头部和尾部)
TreeNode root = queue.pollFirst();
count++;
blist.add(root.val);
if(root.left != null)
queue.offerLast(root.left);
if(root.right != null)
queue.offerLast(root.right);
}else{
TreeNode root = queue.pollLast();
count++;
blist.add(root.val);
if(root.right != null)
queue.offerFirst(root.right);
if(root.left != null)
queue.offerFirst(root.left);
}
if(count == nextCount){
count = 0;
nextCount = queue.size();
alist.add(blist);
blist = new ArrayList<Integer>();
flag = !flag;
}
}
return alist;
}
//方法2
public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer>> alist = new ArrayList<ArrayList<Integer>>();
if(pRoot == null)
return alist;
boolean flag = true;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(pRoot);
int count = 0;
int nextCount = 1;
ArrayList<Integer> blist = new ArrayList<Integer>();
while(!queue.isEmpty()){
TreeNode root = queue.poll();
count++;
blist.add(root.val);
if(root.left != null)
queue.offer(root.left);
if(root.right != null)
queue.offer(root.right);
if(count == nextCount){
count = 0;
nextCount = queue.size();
if(!flag)
alist.add(reverse(blist));
else
alist.add(blist);
blist = new ArrayList<Integer>();
flag = !flag;
}
}
return alist;
}
ArrayList<Integer> reverse( ArrayList<Integer> list){
ArrayList<Integer> res = new ArrayList<Integer>();
for(int i = list.size() - 1; i >= 0; i --)
res.add(list.get(i));
return res;
}