数组、字符串、链表
数组中重复的数字
题目描述
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出可以是2或者3。
分析
基本思路如下:
由于数组长度为n,每个数字的范围都在0到n-1的范围中,所以如果从前向后遍历,将每个数字移到与之下标相同的位置,如果发现这个下标对应的数字与下标相同,说明这个数字重复了。
示例如下:
Java代码
public class Solution {
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
// Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
// 这里要特别注意~返回任意重复的一个,赋值duplication[0]
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
public boolean duplicate(int numbers[],int length,int [] duplication) {
duplication[0] = -1;
if(numbers==null || numbers.length==0)
return false;
int k = 0;
while(k < numbers.length){
int x = numbers[k];
if(x != k){
if(numbers[x] != x)
swap(numbers, k, x);
else{
duplication[0] = x;
return true;
}
}
else
k++;
}
return false;
}
public void swap(int[] array, int i, int j){
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
构建乘积数组
题目描述
给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i] = A[0] * A[1] * … * A[i-1] * A[i+1] * … * A[n-1]。不能使用除法。
分析
定义数组C和数组D,B[i] = C[i]×D[i] = A[0]×A[1]×…×A[i-1] × A[i+1]×…×A[n-2]×A[n-1]
- C[i] = A[0]×A[1]×…×A[i-1]
- D[i] = A[i+1]×…×A[n-2]×A[n-1]
C[i] = (A[0]×A[1]×…×A[i-2]) × A[i-1] = C[i-1] × A[i-1] 从上往下
D[i] = A[i+1] × (A[i+2]…×A[n-2]×A[n-1]) = D[i+1] × A[i+1] 从下往上
Java代码
import java.util.ArrayList;
public class Solution {
public int[] multiply(int[] A) {
int len = A.length;
if(A==null && (len==0||len==1))
return A;
int[] B = new int[len];
int[] C = new int[len];
int[] D = new int[len];
C[0] = 1;
D[len-1] = 1;
for(int i=1; i<len; i++){
C[i] = C[i-1] * A[i-1];
D[len-1-i] = D[len-i] * A[len-i];
}
for(int i=0; i<len; i++){
B[i] = C[i] * D[i];
}
return B;
}
}
正则表达式匹配
题目描述
请实现一个函数用来匹配包括’.‘和’‘的正则表达式。模式中的字符’.‘表示任意一个字符,而’'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但是与"aa.a"和"ab*a"均不匹配
分析
当下一个字符为 ‘*’ 时,当前模式中的字符要分成三种情况考虑,
- 出现0次;
- 出现1次;
- 出现多次;
而下一个字符不为 ‘*’ 时,则将当前的字串中的字符和模式中的字符进行比较:
- 当前模式中的字符不为 ‘.’ 时, 字串中的字符和模式中的字符是否相等;
- 当前模式中的字符为 ‘.’ 时 , 字串中的字符可以为任意字符,但不可以不存在,即字串已遍历结束。
例如,模式为"a.b*c"
则以下几种字符串都是匹配的:
- ‘.‘可以匹配任意字符x,’*’ 对应的b出现0次: axc
- ‘.‘可以匹配任意字符x,’*’ 对应的b出现1次:axbc
- ‘.‘可以匹配任意字符x,’*’ 对应的b出现n次(n>1,以n=3为例):axbbbc
Java代码
public class Solution {
public boolean match(char[] str, char[] pattern)
{
if(str==null || pattern==null)
return false;
return fun(str, 0, pattern, 0);
}
public boolean fun(char[] s, int sidx, char[] p, int pidx){
if(sidx==s.length && pidx==p.length)
return true;
if(sidx!=s.length && pidx==p.length)
return false;
if (pidx+1<p.length && p[pidx+1] == '*') {
if ((sidx!=s.length && p[pidx]==s[sidx]) || (sidx!=s.length && p[pidx]=='.')) {
return fun(s, sidx, p, pidx+2)
|| fun(s, sidx+1, p, pidx+2)
|| fun(s, sidx+1, p, pidx);
} else {
return fun(s, sidx, p, pidx + 2);
}
}
if ((sidx!=s.length && p[pidx]==s[sidx]) || (p[pidx]=='.' && sidx!=s.length)) {
return fun(s, sidx+1, p, pidx+1);
}
return false;
}
}
表示数值的字符串
题目描述
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示数值。 但是"12e",“1a3.14”,“1.2.3”,"±5"和"12e+4.3"都不是。
分析
规定数字格式如下:([] 中的内容可以省略)
第二部分是不可缺少的。
典型的几种错误:
+、±2、1.2.3、12e、12e3.4、1a234
采用从前向后遍历:首先看第一个字符是不是正负号,如果是,在字符串上移动一个字符,继续扫描剩余的字符串中0-9的数位并记录其个数,若个数为0说明该字符串不是数值,若个数不是0则继续扫描。
如果这个数字是小数,则扫描到小数点,小数点只能出现一次。
如果这个数字是用科学记数法标识的,会扫描到’e’或者’E’。
Java代码
class Solution{
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while(sc.hasNext()) {
String str = sc.nextLine();
char[] chars = str.toCharArray();
System.out.println(str + ": " + isNumeric(chars));
}
sc.close();
}
public static boolean isNumeric(char[] str) {
if(str==null || str.length==0)
return false;
boolean res = true;
int k=0;
if(str[k]=='+' || str[k]=='-')
k++;
int temp = scanNumber(str, k);
if(temp == k)
return false;
k = temp;
if(k < str.length) {
if(str[k] == '.') {
k = scanNumber(str, ++k);
if(k<str.length) {
if(str[k]=='E' || str[k]=='e')
res = isExponential(str, ++k);
else
res = false;
}
}
else if(str[k]=='E' || str[k]=='e')
res = isExponential(str, ++k);
else
res = false;
}
return res;
}
public static int scanNumber(char[] array, int idx) {
while(idx<array.length && array[idx]>='0' && array[idx]<='9') {
idx++;
}
return idx;
}
public static boolean isExponential(char[] array, int idx) {
if(idx >= array.length)
return false;
if(array[idx]=='+' || array[idx]=='-')
idx++;
int temp = scanNumber(array, idx);
if(temp == idx || temp!=array.length) {
return false;
}
return true;
}
}
字符流中第一个不重复的字符
题目描述
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
分析
Java代码
public class Solution {
private int[] occurence = new int[256];
private int idx;
public Solution(){
idx = 0;
for(int i=0; i<256; i++)
occurence[i] = -1;
}
//Insert one char from stringstream
public void Insert(char ch)
{
if(occurence[ch] == -1)
occurence[ch] = idx;
else if(occurence[ch] >= 0)
occurence[ch] = -2;
idx++;
}
//return the first appearence once char in current stringstream
public char FirstAppearingOnce()
{
char ch = '#';
int minIdx = Integer.MAX_VALUE;
for(int i=0; i<256; i++){
if(occurence[i]>=0 && occurence[i]<minIdx){
ch = (char)i;
minIdx = occurence[i];
}
}
return ch;
}
}
链表中环中的入口结点
题目描述
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
例如
环的入口结点为3。
链表结点格式:
** public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}**
分析
第一步:
使用两个步长不同的指针,快指针步长为2,慢指针步长为1。起点都是链表的头结点。
如上图所示,快指针和慢指针各走4步之后相遇,此时快指针已经走了1个完整的环,慢指针走了0个完整的环。
由于两个指针的速度不同,所以只要链表中有环,两者一定会相遇。
第二步:
使用两个步长都为1的指针,其中一个指针的起点是链表的头结点,另一个指针的起点是第一步结束时的相遇点。
如上图示例,两个指针走3步之后相遇在环的入口点。
证明:
设:(箭头方向表示链表的方向)
- 从起点到环入口点的距离为 x x x;
- 环的长度为 y y y;
- 相遇点到环入口点的距离为 k k k;
- 第一步快指针和慢指针相遇时,快指针已经走了 n ( n > = 1 ) n(n>=1) n(n>=1) 个完整的环, 慢指针已经走了 m ( m > = 0 ) m(m>=0) m(m>=0) 个完整的环;(上面的示例中, n = 1 n=1 n=1, m = 0 m=0 m=0)
则第一步相遇时,快指针走过的长度
l
e
n
F
a
s
t
=
x
+
n
y
+
k
{\rm len}_{Fast}=x+ny+k
lenFast=x+ny+k,慢指针走过的长度
l
e
n
S
l
o
w
=
x
+
m
y
+
k
{\rm len}_{Slow}=x+my+k
lenSlow=x+my+k。由于快指针步长是慢指针的2倍,所以,
l
e
n
F
a
s
t
=
2
×
l
e
n
S
l
o
w
{\rm len}_{Fast}=2\times {\rm len}_{Slow}
lenFast=2×lenSlow。
也就是
x
+
n
y
+
k
=
2
×
(
x
+
m
y
+
k
)
x+ny+k = 2\times(x+my+k)
x+ny+k=2×(x+my+k)
即
x
=
(
n
−
2
m
)
y
−
k
x=(n-2m)y-k
x=(n−2m)y−k
进一步处理,可得:
x
=
(
n
−
2
m
−
1
)
y
+
(
y
−
k
)
x=(n-2m-1)y+(y-k)
x=(n−2m−1)y+(y−k)
上述公式可以解释第二步为何两个指针相遇在环入口点:
左边
x
x
x:为指针1从链表的头结点开始走过的长度,到达环入口点;
右边
(
n
−
2
m
−
1
)
y
+
(
y
−
k
)
(n-2m-1)y+(y-k)
(n−2m−1)y+(y−k):
- ( y − k ) (y-k) (y−k)表示上图中从相遇点继续走到环入口点的距离;
-
(
n
−
2
m
−
1
)
y
(n-2m-1)y
(n−2m−1)y表示整数个完整的环长,
(
n
−
2
m
−
1
)
(n-2m-1)
(n−2m−1)可以为0。
则指针2从相遇点出发走过 ( y − k ) (y-k) (y−k),到达环入点,再走整数个完整环后仍然到达环入口点。
Java代码
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead)
{
if(pHead == null)
return null;
ListNode fast = pHead;
ListNode slow = pHead;
// 第一步:确定相遇点
do{
if(slow.next == null || fast.next.next == null)
return null;
else{
slow = slow.next;
fast = fast.next.next;
}
}while(fast != slow);
// fast和slow相遇,证明存在环
// 第二步:确定环入口点
fast = pHead;
while(fast != slow){
slow = slow.next;
fast = fast.next;
}
return fast;
}
}
删除链表中重复的结点
题目描述
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
分析
Java代码
public class Solution {
public ListNode deleteDuplication(ListNode pHead)
{
ListNode newHead = new ListNode(0);
newHead.next = pHead;
ListNode p1 = newHead;
ListNode p2 = pHead;
while(p2 != null){
if(p2.next!=null && p2.next.val==p2.val){
while(p2.next!=null && p2.next.val==p2.val){
p2 = p2.next;
}
p1.next = p2.next;
}
else
p1 = p2;
p2 = p2.next;
}
return newHead.next;
}
}