算法是程序员能力的重要体现,学习和工作中要注意锻炼算法能力。
计划分专题完成该本书的题目
一、数组类型
1、数组中的重复数字
题目:在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
分析:数组中数字的值是有限的,又是一个找重复数字的问题,很容易想到哈希法。
用原始数组的值对应新数组的下标,若原始数组中出现重复的数字,那么新数组该下标处对应的元素值为2。
代码:
a、for循环第一次扫描出,统计元素出现的次数
b、在扫描一次hash数组,找到一个出现次数大于1的
public class Solution {
public boolean duplicate(int numbers[],int length,int [] duplication) {
if(numbers == null) return false;
//判断数组是否合法(即数字范围在0到n-1)
for(int i =0; i < numbers.length ; i++){
if(numbers[i]<0 || numbers[i]> length -1){
return false;
}
}
//用hash计数
int[] hash = new int[length];
for(int i = 0 ; i < length; i ++){
hash[numbers[i]] ++;
}
for(int i = 0 ; i < length; i ++){
if(hash[i] > 1){
duplication[0]=i;
return true;
}
}
return false;
}
}
2、二维数组中的查找
题目:在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
分析:二维数组中的查找问题,且是特殊的二维数组,所以去找规律。
上图是查找的过程,我们发现如下规律:首先选取数组中右上角的数字。如果该数字等于要查找的数字,查找过程结束;如果该数字大于要查找的数字,剔除这个数字所在的列;如果该数字小于要查找的数字,剔除这个数字所在的行。也就是说如果要查找的数字不在数组的右上角,则每一次都在数组的查找范围中剔除一行或者一列,这样每一步都可以缩小查找的范围,直到找到要查找的数字,或者查找范围为空。
代码 :
a、拿到右上角的下标
b、target与右上角元素比较,target大,则剔除当前行;target小,则剔除当前列
public class Solution {
public boolean Find(int target, int [][] array) {
if(array == null || array.length == 0 || array[0].length == 0 ){
return false;
}
//初始位置(右上角)
int hang = 0;
int lie = array[0].length -1;
//二维数组的行数
int rows = array.length - 1;
while(hang <= rows && lie >= 0){
if(target == array[hang][lie]){
return true;
}
else if(target > array[hang][lie]){
hang ++;
}
else{
lie --;
}
}
return false;
}
}
二、字符串类型
很多时候字符串和数字是可以相互转换的,它们的解题思路类似。
1、替换空格
题目:请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
分析一:看到字符串替换,会一下想到String的各种方法,好像就有一个替换字符串的方法。
代码一:
注意入参是StringBuffer类型的,要转为String类型操作。
public class Solution {
public String replaceSpace(StringBuffer str) {
/*利用String API的解法*/
//判断
if(str == null) return null;
if( str.length() == 0) return "";
String string = str.toString();
return string.replaceAll(" ","%20");
}
}
分析二:字符串问题可以转为字符数组解决,替换的字符串的本质是对字符数组的操作。
代码二 :
a、统计空格字符的个数
b、创建新数组,大小为原来数组长度+2*空格字符数
c、从后往将原字符数组的元素填充到新数组中
public class Solution {
public String replaceSpace(StringBuffer str) {
String string = str.toString();
if(string.equals("")) return string;
char[] charStr = string.toCharArray();
int i = 0;
int lengthSpace = 0;
//统计空格字符个数
while(i < charStr.length){
if(charStr[i]== ' '){
lengthSpace ++;
}
i ++;
}
int newCharStrLength = charStr.length + lengthSpace * 2 ;
char[] newCharStr = new char[newCharStrLength];
//从后往前遍历原来的数组
int oldIndex = charStr.length - 1;
int newIndex = newCharStrLength -1;
while(oldIndex >= 0){
if(charStr[oldIndex] != ' '){
newCharStr[newIndex --] = charStr[oldIndex --];
}else{
newCharStr[newIndex -- ] = '0';
newCharStr[newIndex -- ] = '2';
newCharStr[newIndex -- ] = '%';
oldIndex --;
}
}
return new String(newCharStr);
}
}
2、翻转单词顺序
题目:
输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. ",则输出"student. a am I"。
分析:
字符串类的问题,转为数组问题,每个单词作为一个字符串,在从后往前拼接字符串。
代码:
class Solution {
public String reverseWords(String s) {
//转为String数组
String[] strArr = s.trim().split(" ");
StringBuilder sb = new StringBuilder();
for(int i = strArr.length-1; i >= 0 ; i --){
if(!strArr[i].equals("")){//特别注意,排除字符串
sb.append(strArr[i]+" ");
}
}
return sb.toString().trim();
}
}
3、表示数值的字符串
题目:
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。
分析:情况很多,典型的分情况讨论,不漏的列出所有情况。
代码:
三、链表类型
1、链表中倒数第k个节点
题目:
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。
示例:
给定一个链表: 1->2->3->4->5, 和 k = 2. 返回链表 4->5.
分析:
如果可以从尾往头遍历,很容易。但是题目要求只能从前往后遍历,可以用两个指针。
1、快指针在head+k处,慢指针在head处,两个指针间的距离维持为K(循环不变量)。
2、两个指针同时往后遍历,当快指针指向null时,慢指针依旧在快指针的前k个结点处(即链表倒数第K个结点)
代码:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode fastHead = head;
ListNode newHead = head;
while(k > 0){
//fastHead在newHead前面K个节点存储,他俩的相对位置作为循环不变量
fastHead = fastHead.next;
k--;
}
//当fastHead指向null时,newHead正好是倒数第k个结点。
while(fastHead != null){
fastHead = fastHead.next;
newHead = newHead.next;
}
return newHead;
}
}
2、反转链表
题目:定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
示例:
输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL
分析:
可以用递归和非递归实现
递归:假设2后面的结点都反转好了(即head.next为头结点的链表反转好了),再只需将2指向1(head.next.next = head),1指向null(head.next = null)
非递归实现:假设1、2、3都已经反转好了,下面开始反转4。(3是新的头结点newHead,4是正在处理的结点curHead)接下来将两个结点向后推进(缩小问题规模),维持处理好的结点和将要处理的结点。
代码:
/**
* public class ListNode {
* int val;
* ListNode next = null;
* ListNode(int val) {
* this.val = val;
* }
* }
*/
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode){
//为了训练链表,先反转,在输出到ArrayList中
ListNode reversedNode = reverseLinkedList(listNode);
ArrayList<Integer> arrayList = new ArrayList<Integer>();
while(reversedNode != null){
arrayList.add(reversedNode.val);
reversedNode = reversedNode.next;
}
return arrayList;
}
/*实现链表反转*/
/* private ListNode reverseLinkedList(ListNode head){
if(head == null|| head.next == null){
return head;
}
ListNode newHead = reverseLinkedList(head.next);
//原链表的第二个结点执行第一个结点,第一个结点指向null
head.next.next = head;
head.next = null;
return newHead;
}
*/ //用循环实现
private ListNode reverseLinkedList(ListNode head){
//newHead是新的链表头,开始时链表无结点为null
ListNode newHead = null;
//当前正在处理的结点
ListNode curHead = head;
while(curHead != null){
//假设newHead前的结点都已经反转了包括newHead(比如4之前都反转好了,现在要反转4)
//步骤:1、将5保存下来;2、4指向3;3、newHead和curHead都往后移一位
ListNode next = curHead.next;
curHead.next = newHead;
newHead = curHead;
curHead = next;
}
return newHead;
}
}
课后题:
https://www.nowcoder.com/practice/0cff324157a24a7a8de3da7934458e34
https://www.nowcoder.com/practice/a632ec91a4524773b8af8694a51109e7
3、删除链表的节点
题目:给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。
示例 1:
输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
分析:
头结点可能就是值为val的结点,需要重新选出头结点。遍历链表维持[head,previous]这个区间,表示到该区间是处理好的不包含val值的结点,下次重previous.next这个节点开始判断。
代码:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode deleteNode(ListNode head, int val) {
//如果开头就是val值结点,那么头结点往后移.
//如果全是val值结点,返回null
while(head.val == val){
head = head.next;
}
if(head == null){
return null;
}
ListNode previous = head; //初始值未定
//区间[head,previous]是已经处理好的,不包含val的,那么previous不为null
while(previous.next != null){
if(previous.next.val == val){
previous.next = previous.next.next;
}else{
//没找到val,previous往前移
previous = previous.next;
}
}
return head;
}
}
四、树
二叉树的遍历,重建二叉树
五、栈和队列