刷题找工资第7篇 – 剑指offer
第一题
第一次出现一次的字符
题目描述
在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).
import java.util.LinkedHashMap;
public class Solution {
public int FirstNotRepeatingChar(String str) {
char[] p = str.toCharArray();
LinkedHashMap<Character,Integer> check = new LinkedHashMap();
int pos = -1;
for(char i:p)
{
int count = check.getOrDefault(i,0);
check.put(i,++count);
}
for(char i:p)
{
if(check.get(i)==1)
return ++pos;
++pos;
}
return pos;
}
}
这个题开始想到时TreeMap,后来用HashMap ,没想到题目问的是位置
可以用TreeMap或者是 LinkedHashMap,因为都是有序的表,下面是两者的运行时间
这是网上关于 TreeMap 和 LinkedHashMap 的一篇文章
各种Map,TreeMap添加删除都是 log(N),因为是 红黑树实现,log(N) 的时间调整树结构,类似于堆,而LinkedHashMap 是一个双向链表,双向链表应该要快
还有一种做法,是看评论C++写的,也用java写了一下,这个indexOf 听说是kmp 算法实现的,当然那是字符串匹配,这个只是查找字符,str中每个位置的字符,我从左边开始找,和从右边开始找,如果这个两个方向找到的位置是一样的,那说明这个字符在字符串中只出现一次。
下面是运行时间和代码
还是挺快的,时间复杂度没分析错应该是 o(N^2)
import java.util.LinkedHashMap;
public class Solution {
public int FirstNotRepeatingChar(String str) {
int len = str.length();
for(int i=0;i<len;++i)
{
char p = str.charAt(i);
if(str.indexOf(p)==str.lastIndexOf(p))
return i;
}
return -1;
}
}
第二题
数组中的逆序对
题目描述
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
输入描述:
题目保证输入的数组中没有的相同的数字
数据范围:
对于%50的数据,size<=10^4
对于%75的数据,size<=10^5
对于%100的数据,size<=2*10^5
举个例子: 1 2 3 4 5 0
由于 1>0, 2>0,3>0…5>0 ,有5个大于0,那么返回 5
题目问的是求 逆序对的对数
思路分析:
直接暴力算法,枚举i,枚举j,双循环枚举每个数
方法二: 归并排序
举个例子,我把一个数组分成两半,左边有序了,右边也排好序了,就要开始合并两个数组了
【1,3,5,6,7,-4,-2,0,2】
我们看到 arr[mid] = 7,从【-4,-2,0】都比 1要小,逆序对为3,注意:后面的3,5,6,7也一定比【-4,-2,0】要大,因此 arr[l] 到arr[mid]中的每一个数都比这个序列要大
//a[i]>a[j]了,那么这一次,从a[i]开始到a[mid]必定都是大于这个a[j]的,因为此时分治的两边已经是各自有序了
public class Solution {
private int P;
public int InversePairs(int [] arr) {
this.P=0;
mergeSort(arr,0,arr.length-1);
return P;
}
public void mergeSort(int[] arr,int l,int r) {
if(r<=l) return;
int m = (l+r)>>>1;
mergeSort(arr,l,m);
mergeSort(arr,m+1,r);
merge(arr,l,m,r);
}
public void merge(int[] arr,int l,int m,int r)
{
int[] temp = new int[r-l+1];
int leftEnd=m++,rightEnd=r;
r=0;
while(l<=leftEnd&&m<=rightEnd)
{
if(arr[l]<arr[m]) temp[r++]=arr[l++];
else {
P+=(leftEnd-l+1);
if(P>1000000007) P%=1000000007;
temp[r++]=arr[m++];
}
}
while(l<=leftEnd) temp[r++]=arr[l++];
while(m<=rightEnd) temp[r++]=arr[m++];
do{
arr[rightEnd--]=temp[--r];
}while(r>0);
}
}
题目3
两个链表的第一个公共节点
题目描述
输入两个链表,找出它们的第一个公共结点。
时间复杂度 O(N) ,空间复杂度 O(N)
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
import java.util.HashSet;
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
if(pHead1==null||pHead2==null) return null;
if(pHead1.next==pHead2) return pHead2;
if(pHead2.next==pHead1) return pHead2;
HashSet<ListNode> set = new HashSet();
ListNode begin=pHead1;
while(begin!=null)
{
set.add(begin);
begin=begin.next;
}
begin = pHead2;
while(begin!=null)
{
if(set.contains(begin)) return begin;
begin= begin.next;
}
return null;
}
}
我看到评论区域还有一种奇妙的做法 ,
举个例子 链表 1长度是 m,链表2长度是 n ,那么 假设 n> m,且 n-m =d,长链表要比短链表多走d步
两个链表从起点开始, 1是短链表,到达终点了就又跑回起点
也就是说,短链表要循环 d轮才能和长链表在同一起跑线上,并且有可能相遇,如果后面同时指向null了,就退出循环,返回null
但是,如果链表成环了怎么办?貌似这个方法不行
还有是用栈的方法,就不写了
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
if(pHead1==null||pHead2==null) return null;
ListNode l = pHead1,r= pHead2;
while(l!=r)
{
l = l==null ? pHead2:l.next;
r = r==null ? pHead1:r.next;
}
return l;
}
}
第4题
数字在排序数组出现的次数
题目描述
统计一个数字在排序数组中出现的次数。
public class Solution {
public int GetNumberOfK(int [] array , int k) {
int ans = 0;
for(int i:array)
{
if(i==k) ++ans;
}
return ans;
}
}
我看到这个题就在想。。怎么这么水。。 尴尬
import java.util.Arrays;
public class Solution {
public int GetNumberOfK(int [] array , int k) {
int ans = 0;
if(array.length==1) return ans= array[0]==k ? 1:0;
int i = Arrays.binarySearch(array,k);
if(i>0){
int l = i-1,r=i+1;
while(l>=0)
{
if(array[l--]==k) ans++;
}
while(r<array.length)
{
if(array[r++]==k) ans++;
}
return ans+1;
}
return ans;
}
}
题目并没有说数组是有序的,如果是有序的只需要 logN 的时间找到这个k,并且如果 k序列长度为 m
只需要 O(m*logN)
但是,如果数组是无序的,使用二分法要排序,时间复杂度是 O(N *logN) ,还不如 遍历
第五题
二叉树的最大深度
题目描述
输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public int TreeDepth(TreeNode root) {
if(root==null) return 0;
return dfs(root);
}
public int dfs(TreeNode root)
{
if(root==null) return 0;
int left = dfs(root.left);
int right = dfs(root.right);
return Math.max(left,right)+1;
}
}
层序遍历一颗二叉树,要求其最大深度,要记住当前层的总的节点个数,当出队的节点个数等于当前层总节点个数了,就数下一次节点个数,开始了下一层的遍历
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.Queue;
import java.util.LinkedList;
public class Solution {
public int TreeDepth(TreeNode root) {
int d=0;
if(root==null) return d;
Queue<TreeNode> q = new LinkedList();
q.offer(root);
int curCount = q.size(),count=0;
while(curCount!=0)
{
TreeNode top = q.poll();
++count;
if(top.left!=null) q.offer(top.left);
if(top.right!=null) q.offer(top.right);
if(count==curCount)
{
++d;
count=0;
curCount= q.size();
}
}
return d;
}
}