应试必备算法
数组
- 删除有序数组的重复元素
public int removeDuplicates(int[] nums) {
int id = 1; //id是要返回的处理后的数组大小
for(int i =1;i<nums.length;i++){
//状态转移逻辑,相等什么都不做,不相等更新nums[id]的值并且id+1
if(nums[i]!=nums[i-1]){
nums[id++] = nums[i];
}
}
return id;
}
- 删除指定元素
//和删除重复的元素有异曲同工之处
public int removeElement(int[] nums, int val) {
int id = 0;
for(int i =0;i<nums.length;i++){
//状态转移逻辑,相等什么都不做,不相等更新nums[id]的值并且id+1
if(nums[i]!=val){
nums[id++] = nums[i];
}
}
return id;
}
- 数组加1
public int[] plusOne(int[] digits) {
int n = digits.length;
for(int i = n-1;i>=0;i--){
if(digits[i]<9){
digits[i]++;
return digits;
}
digits[i]=0;
}
int new_array[] = new int[n+1];
new_array[0]=1;
return new_array;
}
- 数组是否包含重复
public boolean containsDuplicate(int[] nums) {
//运用了hashset
HashSet<Integer> hashSet = new HashSet<Integer>();
for(int i =0;i<nums.length;i++){
if(!hashSet.add(nums[i])){
return true;
}
}
return false;
}
//变试:在数组下标差距为k之内是否有重复
public boolean containsNearbyDuplicate(int[] nums, int k) {
HashSet<Integer>hashSet = new HashSet<Integer>();
for(int i =0;i<nums.length;i++){
if(i>k){
hashSet.remove(nums[i-k-1]);
}
if(!hashSet.add(nums[i])){
return true;
}
}
return false;
}
- 合并两个有序数组(nums1的空间足够大)
public void merge(int[] nums1, int[] nums2,) {
int i = nums1.length-1;
int j = nums2.length-1;
int k = i+j+1;
while(i>=0&&j>=0){
if(nums1[i]<nums2[j]){
nums1[k--] = nums2[j--];
}
else nums1[k--] = nums1[i--];
}
while(j>=0){
nums1[k--] = nums2[j--];
}
}
- 将0移到数组末尾
public void moveZeroes(int[] nums) {
int insertPos = 0;
for(int i = 0;i<nums.length;i++){
if(nums[i]!=0){
nums[insertPos++] = nums[i];
}
}
while(insertPos<nums.length){
nums[insertPos++] = 0;
}
}
- 杨辉三角
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> list = new ArrayList<List<Integer>>();
List<Integer> row,pre = null;
for(int i =1;i<=numRows;i++){
row = new ArrayList<Integer>();
for(int j = 0;j<i;j++){
if(j==0||j==i-1){
row.add(1);
}
else{
row.add(pre.get(j-1)+pre.get(j));
}
}
pre = row;
list.add(row);
}
return list;
}
- 找出第三大的数
public int thirdMax(int[] nums) {
Integer[] max = new Integer[] { null, null, null };
//习惯应用这种迭代方式
for (Integer num : nums) {
//区别==和equals的用法
if (num.equals(max[0]) || num.equals(max[1]) || num.equals(max[2])) {
continue;
}
if (max[0] == null || max[0] < num) {
max[2] = max[1];
max[1] = max[0];
max[0] = num;
} else if (max[1] == null || max[1] < num) {
max[2] = max[1];
max[1] = num;
} else if (max[2] == null || max[2] < num)
max[2] = num;
}
return (max[2] == null ? max[0] : max[2]);
}
- 和为s的两个数的下标
//数组没有排序
public int[] twoSum(int[] numbers, int target) {
int [] res = new int[2];
if(numbers==null||numbers.length<2)
return res;
HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
for(int i = 0; i < numbers.length; i++){
if(!map.containsKey(target-numbers[i])){
map.put(numbers[i],i);
}else{
res[0]= map.get(target-numbers[i])+1;
res[1]= i+1;
break;
}
}
return res;
}
//数组排好了序
public int[] twoSum(int[] numbers, int target) {
int[] result = new int[2];
int left = 0;
int right = numbers.length-1;
//元素不能重复所以left<right
while(left<right){
int v = numbers[left]+numbers[right];
if(v == target){
result[0] = left+1;
result[1] = right+1;
break;
}
else if(v>target)
right--;
else
left++;
}
return result;
}
- 数组中出现次数超过一半的数字
import java.util.HashMap;
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
int result = array[0];
int count =1;
for(int i = 1;i<array.length;i++){
if(array[i] == result){
count++;
}
else count--;
if(count == 0){
result = array[i];
count = 1;
}
}
count = 0;
for(int i = 0 ;i<array.length;i++){
if(array[i] == result) count++;
}
return count >(array.length / 2.0)?result:0;
}
}
- 最长连续1的个数
public int findMaxConsecutiveOnes(int[] nums) {
int count = 0;
int num = 0;
for(int i = 0;i<nums.length;i++){
if(nums[i]==1){
num = Math.max(num,++count);
}
else count=0;
}
return num;
}
- 连续子序列的最大和
//思想:到达array中的某个数值时,如果sum<0,则认为前面的sum无用丢弃,重新开始计算sum,如果sum>0,则认为sum继续更新中,之后与ret比较更新ret
public int FindGreatestSumOfSubArray(int[] array) {
if(array.length == 0) return 0;
int ret = Integer.MIN_VALUE; //用来记录最终的最大和
int sum = 0; //用来记录当前移动的最大和
for(int num : array) {
if(sum <= 0) sum = num;
else sum += num;
ret = Math.max(ret, sum);
}
return ret;
}
字符串
- 字符流中第一个不重复的字符
private int[] cnts = new int[256]; //char的取值范围为-128~127 所以创建256大小的数组用来标记
private Queue<Character> queue = new LinkedList<>();
public void Insert(char ch) {
cnts[ch]++;
queue.add(ch);
while (!queue.isEmpty() && cnts[queue.peek()] > 1) { //我们只关注第一个
queue.poll();
}
}
public char FirstAppearingOnce() {
if (queue.isEmpty()) return '#';
return queue.peek();
}
- 第一个只出现一次的字符位置
//与“字符流中第一个不重复的字符”的区别是:此处有str可以操作,而前者只有char可以操作,也可以理解为静态与动态
public int FirstNotRepeatingChar(String str) {
int[] cnts = new int[256];
for (int i = 0; i < str.length(); i++) cnts[str.charAt(i)]++;
for (int i = 0; i < str.length(); i++) if (cnts[str.charAt(i)] == 1) return i;
return -1;
}
- 最长不含重复字符的子字符串(字符串范围a~z)
public int longestSubStringWithoutDuplication(String str) {
int curLen = 0;
int maxLen = 0;
int[] position = new int[26]; //大小为26的数组用来记录字符在字符串中的位置
for(int i = 0;i<position.length;i++){
position[i]=-1;
}
for (int i = 0; i < str.length(); i++) {
int c = str.charAt(i) - 'a'; //计算字符在数组中的位置
int preIndex = position[c];
if (preIndex == -1 || i - preIndex > curLen) {
curLen++; //连续长度+1
maxLen = Math.max(maxLen, curLen);
} else curLen = i - preIndex; //例如 dabcae 就是4-1
position[c] = i;//记录字符在字符串中的位置
}
return maxLen;
}
- 字符串括号匹配
public boolean isValid(String s) {
Stack<Character> stack = new Stack<Character>();
for(char a : s.toCharArray()){
switch(a){
case '(':stack.push(')');break;
case '{':stack.push('}');break;
case '[':stack.push(']');break;
default:
if(stack.empty()||!stack.pop().equals(a))return false;
}
}
return stack.empty();
}
链表
- 反转链表:输入一个链表,反转链表后,输出链表的所有元素。
public class Solution {
public ListNode ReverseList(ListNode head) {
if(head == null) return null;
ListNode pre,next;
pre = next = null;
while(head!=null){
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
}
- 合并链表:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
ListNode list = new ListNode(0);
ListNode head = list;
while(list1!=null&&list2!=null){
if(list1.val<=list2.val){
list.next = list1;
list1 = list1.next;
}
else {
list.next = list2;
list2 = list2.next;
}
list = list.next;
}
list.next = list1!=null?list1:list2;
return head.next;
}
}
- 反向遍历:输入一个链表,从尾到头打印链表每个节点的值。
import java.util.Stack;
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
Stack<Integer> stack = new Stack<>();
while (listNode != null) {
stack.push(listNode.val);
listNode = listNode.next;
}
ArrayList<Integer> list = new ArrayList<>();
while (!stack.isEmpty()) {
list.add(stack.pop());
}
return list;
}
}
- 链表中倒数第k个结点:输入一个链表,输出该链表中倒数第k个结点。
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
ListNode p,q;
p = q = head;
int i = 0;
for(;p!=null;i++){
if(i>=k)
q = q.next;
p = p.next;
}
return i<k?null:q;
}
}
- 两个链表的第一个公共结点
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode l1 = pHead1, l2 = pHead2;
//每次遍历链表,差距就缩小1
while (l1 != l2) {
if (l1 == null) l1 = pHead2;
else l1 = l1.next;
if (l2 == null) l2 = pHead1;
else l2 = l2.next;
}
return l1;
}
/**
另一种简单思路:首先遍历两个链表得到它们的长度,获得两个链表的长度差值。在第二次遍历的时候,在较长的链表上先走差值步,接着再同时在两个链表上遍历,找到的第一个相同的结点就是它们的第一个公共结点。
**/
- 删除链表结点
//第一种:传入node
public void deleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}
//第二种:传入val
public ListNode removeElements(ListNode head, int val) {
//处理第一个节点为空的情况,并且保证第一个节点的值不为val
while(head!=null && head.val == val){
head = head.next;
}
ListNode curr = head;
while(curr!=null && curr.next!=null){
if(curr.next.val ==val){
curr.next = curr.next.next;
}
else{
curr = curr.next;
}
}
return head;
}
- 删除链表中重复的结点
//第一种:1->1->2->3 处理后变为2->3
public ListNode deleteDuplication(ListNode pHead) {
ListNode first = new ListNode(-1);//设置一个trick
first.next = pHead;
ListNode p = pHead;//p记住当前结点
ListNode last = first;//last记住前面的结点
while (p != null && p.next != null) {
if (p.val == p.next.val) {
int val = p.val;
while (p!= null&&p.val == val)
p = p.next;
last.next = p; //更新p
} else {
last = p; //更新last
p = p.next; //更新p
}
}
return first.next;
}
//第二种:1->1->2->3 处理后变为1->2->3
public ListNode deleteDuplicates(ListNode head) {
if(head==null||head.next==null)return head;
ListNode node = head;
while(node!=null && node.next!=null){
if(node.val == node.next.val){
node.next = node.next.next;
}
else node = node.next;
}
return head;
}
- 链表中环的入口结点
/**
第一步,找环中相汇点。分别用p1,p2指向链表头部,p1每次走一步,p2每次走二步,直到p1==p2找到在环中的相汇点。
第二步,找环的入口。接上步,当p1==p2时,p2所经过节点数为2x,p1所经过节点数为x,设环中有n个节点 ,p2比p1多走一圈有2x=n+x; n=x;可以看出p1实际走了一个环的步数,再让p2指向链表头部,p1位置不变,p1,p2每次走一步直到p1==p2; 此时p1指向环的入口。
**/
public ListNode EntryNodeOfLoop(ListNode pHead) {
if (pHead == null) return null;
ListNode slow = pHead, fast = pHead;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (slow == fast) {
fast = pHead;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
return null;
}
栈与队列
- 栈实现队列
/**
思路:栈的特点是先进后出,push的元素进入一个栈A,要满足队列先进先出
的特点,还需要另外一个栈B,pop时将栈A元素出栈到栈B,之后栈B在进行出
栈,就有了队列先进先出的效果
**/
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(stack1.empty()&&stack2.empty()){
throw new RuntimeException("Queue is empty!");
}
if(stack2.empty()){
while(!stack1.empty()){
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
}
- 队列实现栈
/**
思路:假设有两个队列Q1和Q2,当二者都为空时,入栈操作可以用入队操作来模拟,可以随便选一个空队列,假设选Q1进行入栈操作,现在假设a,b,c依次入栈了(即依次进入队列Q1),这时如果想模拟出栈操作,则需要将c出栈,因为在栈顶,这时候可以考虑用空队列Q2,将a,b依次从Q1中出队,而后进入队列Q2,将Q1的最后一个元素c出队即可,此时Q1变为了空队列,Q2中有两个元素,队头元素为a,队尾元素为b,接下来如果再执行入栈操作,则需要将元素进入到Q1和Q2中的非空队列,即进入Q2队列,出栈的话,就跟前面的一样,将Q2除最后一个元素外全部出队,并依次进入队列Q1,再将Q2的最后一个元素出队即可
**/
public class Solution {
Queue<Integer> queue1 = new LinkedList<Integer>();
Queue<Integer> queue2 = new LinkedList<Integer>();
public void push(int node) {
if (queue1.isEmpty()&&queue2.isEmpty()) {
queue1.add(node);
return;
}
if (queue1.isEmpty()) {
queue2.add(node);
return;
}
if (queue2.isEmpty()) {
queue1.add(node);
return;
}
}
public int pop() {
if (queue1.isEmpty()&&queue2.isEmpty()) {
try {
throw new Exception("stack is empty");
} catch (Exception e) {
}
}
if (queue1.isEmpty()) {
while (queue2.size()>1) {
queue1.add(queue2.poll());
}
return queue2.poll();
}
if (queue2.isEmpty()) {
while (queue1.size()>1) {
queue2.add(queue1.poll());
}
return queue1.poll();
}
return (Integer) null;
}
- 栈的压入,弹出序列
//给出一个入栈序列和一个出栈序列,判断是否匹配
public boolean IsPopOrder(int[] pushA, int[] popA) {
int n = pushA.length;
Stack<Integer> stack = new Stack<>();
for (int i = 0, j = 0; i < n; i++) {
stack.push(pushA[i]);
while (j < n && stack.peek() == popA[j]) {
stack.pop();
j++;
}
}
return stack.isEmpty();
}
二叉树
- 先序遍历
//recursive
public void visit(TreeNode root){
if(root == null) return ;
System.out.println(root.val);
visit(root.left);
visit(root.right);
}
//iterative
public void visit(TreeNode node){
Stack<TreeNode> stack =new Stack<TreeNode>();
while(root!=null || !stack.empty()){
while(root!=null){
System.out.println(root.val);
stack.push(root);
root = root.left;
}
root = stack.pop();
root = root.right;
}
}
- 中序遍历
//recursive
public void visit(TreeNode root){
if(root ==null)return;
visit(root.left);
System.out.println(root.val);
visit(root.right);
}
//iterative
public void visit(TreeNode node){
Stack<TreeNode> stack =new Stack<TreeNode>();
while(root!=null || !stack.empty()){
while(root!=null){
stack.push(root);
root = root.left;
}
root = stack.pop();
System.out.println(root.val);
root = root.right;
}
}
- 后序遍历
//recursive
public void visit(TreeNode root){
if(root == null) return ;
visit(root.left);
visit(root.right);
System.out.println(root.val);
}
//iterativey
public void visit(TreeNode node){
Stack<TreeNode> stack = new Stack<TreeNode>();
Stack<TreeNode>stack1 = new Stack<TreeNode>();
while(root!=null || !stack.empty()){
while(root!=null){
stack.push(root);
stack1.push(root);
root = root.right;
}
root = stack.pop();
root = root.left;
}
while(!stack1.empty()){
System.out.println(stack1.pop().val);
}
}
- 层次遍历
public void visit(TreeNode root){
if(root == null)return;
Queue<TreeNode> q = new LinkedList<TTreeNode>();
q.offer(root);
while(!q.isEmpty()){
TreeNode node = q.poll();
System.out.println(node.val);
if(node.left!=null){
q.offer(node.left);
}
if(node.right!=null){
q.offer(node.right);
}
}
}
- 二叉搜索树的第k个结点
//递归
public class Solution {
int cnt = 0;
TreeNode KthNode(TreeNode root, int k) {
return inorder(root, k);
}
private void inorder(TreeNode root, int k) {
if (root == null) return;
inorder(root.left, k);
cnt++;
if (cnt == k) return root;
inorder(root.right, k);
}
}
//递推
public TreeNode KthNode(TreeNode root,int k){
int cnt = 0;
Stack<TreeNode> stack =new Stack<TreeNode>();
while(root!=null || !stack.empty()){
while(root!=null){
stack.push(root);
root = root.left;
}
root = stack.pop();
cnt++;
if (cnt == k) return root;
root = root.right;
}
return null;
}
- 二叉树的深度
//递归
public int TreeDepth(TreeNode pRoot)
{
if(pRoot == null){
return 0;
}
int left = TreeDepth(pRoot.left);
int right = TreeDepth(pRoot.right);
return Math.max(left, right) + 1;
}
//递推(层次遍历)
public int TreeDepth(TreeNode pRoot)
{
if(pRoot == null){
return 0;
}
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.add(pRoot);
//depth表示深度,count表示当前层已经遍历的结点个数,nextCount表示下一层的结点个数
int depth = 0, count = 0, nextCount = 1;
while(queue.size()!=0){
TreeNode top = queue.poll();
count++;
if(top.left != null){
queue.add(top.left);
}
if(top.right != null){
queue.add(top.right);
}
if(count == nextCount){//当前层次结点已经遍历完了
nextCount = queue.size(); //更新nextCount
count = 0; //count变为0
depth++;//深度+1
}
}
return depth;
}
- 平衡二叉树:输入一棵二叉树,判断该二叉树是否是平衡二叉树。
public class Solution {
//后续遍历时,遍历到一个节点,其左右子树已经遍历 依次自底向上判断,每个节点只需要遍历一次
private boolean isBalanced=true;
public boolean IsBalanced_Solution(TreeNode root) {
getDepth(root);
return isBalanced;
}
public int getDepth(TreeNode root){
if(root==null)
return 0;
int left=getDepth(root.left);
int right=getDepth(root.right);
if(Math.abs(left-right)>1){
isBalanced=false;
}
return right>left ?right+1:left+1;
}
}
- 二叉树的镜像:操作给定的二叉树,将其变换为源二叉树的镜像。
//先序遍历的变型
public class Solution {
public void Mirror(TreeNode root) {
if(root == null)return;
change(root);
Mirror(root.left);
Mirror(root.right);
}
private void change(TreeNode root){
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
}
}
- 二叉搜索树的后序遍历序列
public class Solution {
public boolean VerifySquenceOfBST(int [] sequence) {
int size = sequence.length;
int i =0 ;
if(size == 0)return false;
while(--size>0){
while(sequence[i++]<sequence[size]){
if(i==size)break;
};
while(sequence[i++]>sequence[size]){
if(i==size)break;
};
if(i<size)return false;
i = 0;
}
return true;
}
}
排序
重要概念(稳定性)排序算法稳定性的简单形式化定义为:如果Ai = Aj,排序前Ai在Aj之前,排序后Ai还在Aj之前,则称这种排序算法是稳定的。通俗地讲就是保证排序前后两个相等的数的相对顺序不变。
- 冒泡排序
//初始
public void BubbleSort(int[] a) {
for(int i = 0;i<a.length;i++) {
for(int j=0;j<a.length-1-i;j++) {
if(a[j]>a[j+1]) {
int tem=a[j];
a[j]=a[j+1];
a[j+1]=tem;
}
}
}
}
//优化:最优情况时间复杂度O(n)
public void BubbleSort(int[] a) {
boolean flag = true;
for(int i = 0;i<a.length;i++) {
flag = false;
for(int j=0;j<a.length-1-i;j++) {
if(a[j]>a[j+1]) {
int tem=a[j];
a[j]=a[j+1];
a[j+1]=tem;
flag = true;
}
}
if(!flag)break;
}
}
- 选择排序
/**
注意选择排序与冒泡排序的区别:冒泡排序通过依次交换相邻两个顺序不合法的元素位置,从而将当前最小(大)元素放到合适的位置;而选择排序每遍历一次都记住了当前最小(大)元素的位置,最后仅需一次交换操作即可将其放到合适的位置。
**/
public void SelectSort(int[] a) {
int minIndex = 0;
int temp = 0;
if ((a == null) || (a.length == 0))
return;
for (int i = 0; i < a.length - 1; i++) {
minIndex = i;
for (int j = i + 1; j < a.length; j++) {
if (a[j] < a[minIndex]) {
minIndex = j;
}
}
if (minIndex != i) {
temp = a[i];
a[i] = a[minIndex];
a[minIndex] = temp;
}
}
}
/**
选择排序是不稳定的排序算法,不稳定发生在最小元素与A[i]交换的时刻。
比如序列:{ 5, 8, 5, 2,9},一次选择的最小元素是2,然后把2和第一个5进行交换,从而改变了两个元素5的相对次序
**/
- 插入排序
public static void InsertSort(int[] arr)
{
int j;
int target;
for (int i = 1; i < arr.length; i++) {
j = i;
target = arr[i];
while (j > 0 && target < arr[j - 1]){
arr[j] = arr[j - 1];
j--;
}
arr[j] = target;
}
}
- 归并排序
public int[] sort(int[] a,int low,int high){
int mid = (low+high)/2;
if(low<high){
sort(a,low,mid);
sort(a,mid+1,high);
merge(a,low,mid,high);
}
return a;
}
public void merge(int[] a, int low, int mid, int high) {
int[] temp = new int[high-low+1];
int i= low;
int j = mid+1;
int k=0;
while(i<=mid && j<=high){
if(a[i]<a[j]){
temp[k++] = a[i++];
}else{
temp[k++] = a[j++];
}
}
while(i<=mid){
temp[k++] = a[i++];
}
while(j<=high){
temp[k++] = a[j++];
}
for(int x=0;x<temp.length;x++){
a[x+low] = temp[x];
}
}
- 快速排序
/**
基本思想:
快速排序使用分治的思想,通过一趟排序将待排序列分割成两部分,其中一部分记录的关键字均比另一部分记录的关键字小。之后分别对这两部分记录继续进行排序,以达到整个序列有序的目的。
快排的步骤:
(1)选择基准:在待排序列中,按照某种方式挑出一个元素,作为 “基准”(pivot)
(2)分割操作:以该基准在序列中的实际位置,把序列分成两个子序列。此时,在基准左边的元素都比该基准小,在基准右边的元素都比基准大
(3)递归地对两个序列进行快速排序,直到序列为空或者只有一个元素。
时间复杂度:
最好情况:平衡二叉树:T(n) <= 2*T(n/2) + O(n) 计算得O(nlgn)
最差情况:斜树直线:T(n)=T(n-1)+n-1 计算得O(n^2)
**/
public static void sort(int[] s, int lo, int hi){
int left = lo;
int right = hi;
int temp;
if(lo<hi){
while(left<right){
while(left<hi&&s[left]<=s[lo]){
left++;
}
while(right>lo&&s[right]>s[lo]){
right--;
}
if(left<right){//left<right是为了避免最后的重复交换
temp = s[left];
s[left] = s[right];
s[right] = temp;
}
}
//下面将s[0]的值与s[right]交换
temp = s[lo];
s[lo] = s[right];
s[right] = temp;
sort(s, lo, right-1);
sort(s, right+1, hi);
}
}
/**
快排的优化:
从快排的思想来看对于基准点的选取是至关重要的,因为他会将序列进行分割从而决定了递归的深度,而快排的时间复杂度是:depth*O(n),常见的优化方法有:随机选取基准,三数取中选取基准,当序列分割到一定大小时用插入排序,每次分割结束将key相等的元素聚在一起
**/
- 堆排序
/**
堆排序的思想:利用堆的特性,父结点值大于(或者小于)子结点值,依次取出堆顶的值,再维护成一个新堆,重复该过程,数据就被排序了
堆排序需要解决两个问题:
1.如何由一个无序序列建成一个堆?
2.如何在输出堆顶元素之后,调整剩余元素成为一个新的堆
堆排序的效率问题:
1.构建堆 O(n) 解法:递推公式为T(n) = 2*T(n/2) + O(lg n)
2.堆排序 O(nlgn) 解法:log(n-1)+log(n-2)+log(n-3)+...+lg1<nlgn
对排序是不稳定的:比如序列:{ 9, 5, 7, 5 },堆顶元素是9,堆排序下一步将9和第二个5进行交换,得到序列 { 5, 5, 7, 9 },再进行堆调整得到{ 7, 5, 5, 9 },重复之前的操作最后得到{ 5, 5, 7, 9 }从而改变了两个5的相对次序
**/
public static void sort(int[] a) {
//下标小于a.length/2的为非叶子结点,需要调整子结点建立堆
for (int i = a.length / 2; i >= 0; i--) {
adjustHeap(a, i, a.length - 1);
}
// 进行n-1次循环完成排序
for (int i = a.length - 1; i > 0; i--) {
// 最后一个元素和第一个元素进行交换
int temp = a[i];
a[i] = a[0];
a[0] = temp;
adjustHeap(a, 0, i);//调整根结点,维护成一个堆
}
}
//调整为大根堆
public static void adjustHeap(int[] a, int parent, int length) {
int temp = a[parent]; // temp保存父节点的值
int child = 2 * parent + 1; // 左子节点的索引
while (child < length) {// 如果child>=length说明不需要调整了
//比较左右结点记住最大结点的下标
if (child + 1 < length && a[child] < a[child + 1]) {
child ++;// child指向右结点
}
// 如果父结点的值大于子结点,直接返回
if (temp > a[child])
break;
//否则,将子节点的值赋值给父节点
a[parent] = a[child];
// 递推调整子结点的堆结构
parent = child;
child = 2 * parent + 1;
}
//将开始记住父结点值的temp值赋给当前a[parent]
a[parent] = temp;
}
- 最小的k个数
/**
快排:利用快排的思想,寻找第k个位置上正确的数,k位置前面的数即是比k位置小的数组,k后面的数即是比k位置元素大的数组
**/
public void sort(int[] s, int lo, int hi, int k){
int left = lo;
int right = hi;
int temp;
if(lo<hi){
while(left<right){
while(left<hi&&s[left]<=s[lo]){
left++;
}
while(right>lo&&s[right]>s[lo]){
right--;
}
if(left<right){//left<right是为了避免最后的重复交换
temp = s[left];
s[left] = s[right];
s[right] = temp;
}
}
//下面将s[0]的值与s[right]交换
temp = s[lo];
s[lo] = s[right];
s[right] = temp;
while (right != k - 1) {
if (right > k - 1) {
hi = right-1;
sort( s, lo, hi, k);
} else {
lo = right+1;
sort( s, lo, hi, k);
}
}
}
}
//堆排序:先构造大小为k的堆,之后遍历数组调整堆
public static void sort(int[] a,int k) {
int []maxHeap = new int[k]; //储存堆的数组
for (in i = 0; i < maxHeap.lengtth; i++) {
maxHeap[i] = a[i]; //初始化
}
for (int i = a.length / 2; i >= 0; i--) {
adjustHeap(a, i, a.length - 1); //维护成一个最大堆
}
for (int i = k; i <a.length ; i++) {
if (maxHeap[0]>a[i]) {
maxHeap[0] = a[i];//如果a[i]中的元素比堆中根结点的数值小,入堆
adjustHeap(maxHeap, 0);
}
}
//调整为大根堆
public static void adjustHeap(int[] a, int parent, int length) {
int temp = a[parent]; // temp保存父节点的值
int child = 2 * parent + 1; // 左子节点的索引
while (child < length) {// 如果child>=length说明不需要调整了
//比较左右结点记住最大结点的下标
if (child + 1 < length && a[child] < a[child + 1]) {
child ++;// child指向右结点
}
// 如果父结点的值大于子结点,直接返回
if (temp > a[child])
break;
//否则,将子节点的值赋值给父节点
a[parent] = a[child];
// 递推调整子结点的堆结构
parent = child;
child = 2 * parent + 1;
}
//将开始记住父结点值的temp值赋给当前a[parent]
a[parent] = temp;
}
/**
两种思路对比:
快排
优点:节省空降,时间复杂度平均为O(n+n/2+n/(2^2)+...+n/(2^lgn))=O(n)
缺点:需要修改原始数组
堆排
优点:不用修改原始数组,适合海量数据
缺点:时间复杂度略高 O(k+(n-k)lgk)=O(nlogk)
**/
二分法及变形
- 有序数组中查找
//找到返回下标,否则返回应该插入的下标
public int searchInsert(int[] nums, int target) {
int low = 0;
//high = nums.length -1注意要减一
int high = nums.length-1;
//判断条件是小于等于
while(low<=high){
int mid = (low+high)/2;
if(nums[mid]==target){
return mid;
}
else if(nums[mid]<target){
low=mid+1;
}
else {
high=mid-1;
}
}
return low;
}
- 矩阵二分查找:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
public class Solution {
public boolean Find(int target, int [][] array) {
int row = 0;
int column = array[0].length-1;
while(row<array.length && column>=0){
if(array[row][column] == target){
return true;
}
else if(array[row][column]<target){
row++;
}
else column--;
}
return false;
}
}
- 数字在排序数组中出现的次数
public int GetNumberOfK(int[] array, int k) {
//下述二分找出一个k的下标
int l = 0, h = array.length - 1;
while (l <= h) {
int m = l + (h - l) / 2;
if (array[m] >= k) h = m - 1; //注意是>=
else l = m + 1;
}
int cnt = 0;
while (l < array.length && array[l++] == k) cnt++;
return cnt;
}
- 求x的开方
public int mySqrt(int x) {
if(x == 0) return 0;
int i = 1;
int j = x;
while(i<=j){
int mid = i + (j - i)/2;
if(mid>x/mid){
j = mid - 1;
}
else{
if((mid+1) > x/(mid+1))
return mid;
i = mid + 1;
}
}
return -1;
}
//变试:判断一个数是不是完全平方数
public boolean isPerfectSquare(int num) {
int low = 1, high = num;
while (low <= high) {
long mid = (low + high)/2;
if (mid * mid == num) {
return true;
} else if (mid * mid < num) {
low = (int) mid + 1;
} else {
high = (int) mid - 1;
}
}
return false;
}
动态规划
- 斐波那契数列:现在要求输入一个整数n,请你输出斐波那契数列的第n项。n<=39
//动态规划,每个数值都是由前两个数值决定的,具有最优子序列
//g+=f就是加上前两个数值
//f=g-f是为了记住当前g的前一个数值,对下一个g来说就是前第个数值了
public class Solution {
public int Fibonacci(int n) {
int f = 0, g = 1;
while(--n>=0) {
g += f;
f = g - f;
}
return f;
}
}
- 跳台阶:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
public class Solution {
public int JumpFloor(int target) {
int f = 1 ; int g = 2;
while(--target>0){
g+=f;
f = g-f;
}
return f;
}
}
- 变态跳台阶:一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
public class Solution {
public int JumpFloorII(int target) {
int a [] = new int[target];
a[0]=1;
if(target>1){
a[1]=2;}
for(int i = 2;i<target;i++){
for(int j =0;j<i;j++){
a[i]+=a[j];
}
a[i]+=1;
}
return a[target-1];
}
}
其他
- 二进制中1的个数:输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
public class Solution {
public int NumberOf1(int n) {
int count = 0;
while(n!= 0){
count++;
n = n & (n - 1);
}
return count;
}
}