3.数组中重复的数字
3-1 找出数组中重复的数字
思路:交换下标,时间复杂度 O(N),空间复杂度 O(1)。因此不能使用排序的方法,也不能使用额外的标记数组。对于这种数组元素在 [0, n-1] 范围内的问题,可以将值为 i 的元素调整到第 i 个位置上进行求解。以 (2, 3, 1, 0, 2, 5) 为例,遍历到位置 4 时,该位置上的数为 2,但是第 2 个位置上已经有一个 2 的值了,因此可以知道 2 重复:
class Solution {
public int duplicateInArray(int[] nums) {
for(int i =0; inums.length-1)
return -1;
}
int dumplication=0;
for(int i=0; i< nums.length; i++){
while(nums[i]!=i){
if(nums[nums[i]]==nums[i]){
dumplication = nums[i];
return dumplication;
}
else{
int tmp = nums[i];
nums[i] = nums[tmp];
nums[tmp] = tmp;
}
}
}
return -1;
}
}
3-2 不修改数组找出重复的数字
思路:抽屉原理+分治法+区间不相交;按数字划分1-n;时间复杂度O(nlogn);空间复杂度O(1);不保证找出所有重复数字。
class Solution {
public int duplicateInArray(int[] nums) {
int begin = 1;
int end = nums.length-1;
while(end >= begin){
int mid = (end+begin)>>1;//除以二
int count = countRange(nums,begin,mid);
if(end == begin){
if(count>1) return begin;
else return -1;
}
if(count>(mid-begin+1)){
end = mid;
}
else{
begin = mid+1;
}
}
return -1;
}
public static int countRange(int[] nums, int begin, int end){
int count=0;
for(int i = 0; i< nums.length; i++){
if(nums[i]>=begin && nums[i]<=end){
count++;
}
}
return count;
}
}
4.二维数组中的查找
思路:发现每个子矩阵右上角的数的性质:x左边的数都小于等于x,x下边的数都大于等于x。如果 x等于target,则说明我们找到了目标值,返回true;如果 x 小于target,则 x左边的数一定都小于target,我们可以直接排除当前一整行的数;如果 x大于target,则 xx 下边的数一定都大于target,我们可以直接排序当前一整列的数。时间复杂度O(m+n)。
class Solution {
public boolean searchArray(int[][] array, int target) {
int newrow = 0;
int newcol = array.length-1;
int i = 0;
while(newrow < array.length && newcol>=0){
if(array[newrow][newcol] == target){
return true;
}
else if(array[newrow][newcol] > target){
newcol = newcol-1;
}
else{
newrow = newrow+1;
}
}
return false;
}
}
5. 替换空格
思路:双指针。先把stringbuffer扩展(append),在用两个分别指向两个stringbuffer尾部的指针向前移动。时间复杂度O(n)。
class Solution {
public String replaceSpaces(StringBuffer str) {
int P1 = str.length()-1;
for(int i = 0; i < P1+1; i++){
if(str.charAt(i)==' '){
str.append(" ");
}
}
int P2 = str.length()-1;
while(P1!=P2 && P1>=0 && P2>=0){
if(str.charAt(P1)!=' '){
str.setCharAt(P2,str.charAt(P1));
P2--;
P1--;
}
else{
P1--;
str.setCharAt(P2, '0');
str.setCharAt(P2-1, '2');
str.setCharAt(P2-2, '%');
P2 = P2-3;
}
}
return str.toString();
}
}
6.从尾到头打印链表
思路:使用栈存储,后进先出,(基于递归的话,函数调用栈很长,可能导致溢出),用栈实现鲁棒性会更好。
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] printListReversingly(ListNode head) {
Stack stack = new Stack<>();
int i = 0;
while(head!=null){
stack.push(head.val);
head = head.next;
i++;
}
int[] result = new int[i];
int j=0;
while(!stack.empty()){
result[j] = stack.pop();
j++;
}
return result;
}
}
7. 重建二叉树
思路:递归建立整棵二叉树:先递归创建左右子树,然后创建根节点,并让指针指向两棵子树。先利用前序遍历找根节点:前序遍历的第一个数,就是根节点的值;在中序遍历中找到根节点的位置 k,则 k 左边是左子树的中序遍历,右边是右子树的中序遍历;假设左子树的中序遍历的长度是 l,则在前序遍历中,根节点后面的 l 个数,是左子树的前序遍历,剩下的数是右子树的前序遍历;
有了左右子树的前序遍历和中序遍历,我们可以先递归创建出左右子树,然后再创建根节点。时间复杂度O(n)。
/**
* Definition for a binary tree node.
* class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public Map findIndex = new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
for(int i=0; ipr) return null;
int k = findIndex.get(preorder[pl]);
TreeNode root = new TreeNode(preorder[pl]);
TreeNode left = reconstruct(preorder, inorder, pl+1, pl+k-il, il, k-1);
TreeNode right = reconstruct(preorder, inorder, pl+k-il+1, pr,k+1, ir);
root.left = left;
root.right = right;
return root;
}
}
8. 二叉树的下一个节点
思路:时间复杂度O(h)。这道题目就是让我们求二叉树中给定节点的后继。分情况讨论即可。如果当前节点有右儿子,则右子树中最左侧的节点就是当前节点的后继。比如F的后继是H;如果当前节点没有右儿子,则需要沿着father域一直向上找,找到第一个是其father左儿子的节点,该节点的father就是当前节点的后继。比如当前节点是D,则第一个满足是其father左儿子的节点是C,则C的father就是D的后继。(8的后继是3)
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode father;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode inorderSuccessor(TreeNode p) {
if(p.right != null){
p = p.right;
while(p.left != null){
p = p.left;
}
return p;
}
else if(p.father != null){
if(p == p.father.left){
return p.father;
}
else{
while(p.father != null){
if(p.father == p.father.left) return p.father;
p = p.father;
}
}
}
return null;
}
}
9. 用两个栈实现队列
class MyQueue {
/** Initialize your data structure here. */
public Stack stack1 = new Stack();
public Stack stack2 = new Stack();
public MyQueue() {
}
/** Push element x to the back of queue. */
public void push(int x) {
while(!stack1.empty()){
stack2.push(stack1.peek());
stack1.pop();
}
stack1.push(x);
while(!stack2.empty()){
stack1.push(stack2.peek());
stack2.pop();
}
}
/** Removes the element from in front of queue and returns that element. */
public int pop() {
int num = stack1.peek();
stack1.pop();
return num;
}
/** Get the front element. */
public int peek() {
return stack1.peek();
}
/** Returns whether the queue is empty. */
public boolean empty() {
return stack1.empty();
}
}
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue obj = new MyQueue();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.peek();
* boolean param_4 = obj.empty();
*/
10.斐波那契数列
10-1 求斐波那契数列的第n项
思路:循环,记录前一个和前两个数。 不用递归。递归是函数调用自身,调用存在时间和空间小号,每调用一次,都需要在内存占中分配空间保存参数、返回地址、和临时变量,而且入栈和出栈都需要时间,效率不如循环。递归也可能引起调用栈溢出。时间复杂度O(n)
class Solution {
public int Fibonacci(int n) {
if(n==0) return 0;
if(n==1) return 1;
int fnminus1 = 1;
int fnminus2 = 0;
int fn = 0;
for(int i=2; i<=n; i++){
fn = fnminus1 + fnminus2;
fnminus2 = fnminus1;
fnminus1 = fn;
}
return fn;
}
}
10-2 青蛙跳台阶&&矩形覆盖
思路:解法和斐波那契数列类似。
10-3 变态跳台阶
public int JumpFloorII(int target) {//动态规划
int[] dp = new int[target];
Arrays.fill(dp, 1);
for (int i = 1; i < target; i++)
for (int j = 0; j < i; j++)
dp[i] += dp[j];
return dp[target - 1];
}
public int JumpFloorII(int target) {//数学推导
return (int) Math.pow(2, target - 1);
}
11.旋转数组的最小数字
思路:画图,注意特殊情况(1)12345 (2)3412333(3)1111123;二分的时间复杂度是 O(logn),删除最后水平一段的时间复杂度最坏是 O(n),所以总时间复杂度是 O(n)。
图中水平的实线段表示相同元素。我们发现除了最后水平的一段(黑色水平那段)之外,其余部分满足二分性质(不相交):竖直虚线左边的数满足 nums[i]≥nums[0];而竖直虚线右边的数不满足这个条件。分界点就是整个数组的最小值。所以我们先将最后水平的一段删除即可。另外,不要忘记处理数组完全单调的特殊情况:当我们删除最后水平的一段之后,如果剩下的最后一个数大于等于第一个数,则说明数组完全单调。
class Solution {
public int findMin(int[] nums) {
if(nums.length == 0) return -1;
int size = nums.length - 1;
if(nums[size] > nums[0]) return nums[0];//情况1
while(size > 0 && nums[size] == nums[0]) size--;//情况2
int left = 0;
int right = size;
while(left < right){
int mid = right + left >> 1;
if(nums[mid] >= nums[0]) left = mid + 1; //情况3
else right = mid;
}
return nums[right];
}
}
12.矩阵中的路径
思路:回溯法,dfs,dfs用递归、栈来解决。
在深度优先搜索中,最重要的就是考虑好搜索顺序。我们先枚举单词的起点,然后依次枚举单词的每个字母。
过程中需要将已经使用过的字母改成一个特殊字母,以避免重复使用字符。
class Solution {
public boolean hasPath(char[][] matrix, String str) {
for(int i=0; i=0 && i+xRange[range]=0 && j+yRange[range]
13.机器人的运动范围
思路:回溯法,bfs,数据范围比较大,适合用宽搜,没有爆栈的风险,用队列存储。时间复杂度O(mn)。
class Solution {
public int movingCount(int threshold, int rows, int cols)
{
if(rows==0 || cols==0) return 0;
Queue queue = new LinkedList();
int[] pair = {0, 0};
queue.add(pair);
int count = 1;
boolean[][] visited = new boolean[rows][cols];
for(int i=0; i=0 && x=0 && y threshold) return false;
else return true;
}
}
14.剪绳子
思路:(1)动态规划;O(n²)
(2)贪心算法:如果n>=5;那么3(n-3)>n && 2(n-2)>n && 3(n-3)>2(n-2),所以n>=5时,尽可能把绳子剪成长度为3的;当剩下的绳子长度为4时,把绳子剪成两段长度为2的。O(n)
class Solution {//dp
public int maxProductAfterCutting(int length)
{
if(length<2) return 0;
if(length==2) return 1;
if(length==3) return 2;//必须剪一刀
int[] product = new int[length+1];
product[1] = 1;
product[2] = 2;
product[3] = 3;//不剪收益最高
for(int i=4; i<=length; i++){
int max=0;
for(int j=1; j<=i/2; j++){
if(product[j]*product[i-j]>max)
max = product[j]*product[i-j];
}
product[i] = max;
}
return product[length];
}
}
class Solution {//贪心
public int maxProductAfterCutting(int length)
{
if(length==2) return 1;
if(length==3) return 2;
if(length==4) return 4;
int num3 = length/3;
int result = 1;
if(length-num3*3 == 1) num3-=1;
int num2 = (length - num3*3)/2;
for(int i=0; i
15. 二进制中1的个数
思路:(1)常规解法: (造成死循环)右移一个负整数,系统会自动在最高位补1,这样会导致 n 永远不为0,就死循环了。
(2)把最右面的1变成0:n&(n-1),时间复杂度O(m)(m为1的个数)【二进制的数减去一:最右边的1变成0,这位右边的0都变成1,左边的数保持不变。发现n与n-1做位与运算,相当于把最右边的1变成0。】
class Solution {
public int NumberOf1(int n)
{
int count = 0;
while(n!=0){
n = (n-1)&n;
count++;
}
return count;
}
}