今日算法題 1248 優美子數組
給你一個整數數組 nums
和一個整數 k
。
如果某個 連續 子數組中恰好有 k
個奇數數字,我們就認爲這個子數組是「優美子數組」。
請返回這個數組中「優美子數組」的數目。
示例 1:
輸入:nums = [1,1,2,1,1], k = 3 輸出:2 解釋:包含 3 個奇數的子數組是 [1,1,2,1] 和 [1,2,1,1] 。
示例 2:
輸入:nums = [2,4,6], k = 1 輸出:0 解釋:數列中不包含任何奇數,所以不存在優美子數組。
示例 3:
輸入:nums = [2,2,2,1,2,2,1,2,2,2], k = 2 輸出:16
提示:
1 <= nums.length <= 50000
1 <= nums[i] <= 10^5
1 <= k <= nums.length
public class NumberOfSubarrays {
//第一種暴力破解 自己想出來的,但是提交時用例超時
public static int numberOfSubarrays(int[] nums, int k) {
int result = 0 ;
int index = 0 ;
int tempIndex = index;
int flag = 0;
while (index < nums.length ){
if(nums[tempIndex]%2 == 1){
flag++;
}
if(flag == k){
result++;
}else if(flag > k){
index++;
flag = 0;
tempIndex = index;
continue;
}
tempIndex++;
if(tempIndex >= nums.length){
index++;
flag = 0;
tempIndex = index;
}
}
return result;
}
//第二種爲使用數據計算方式
//TODO (odd[i]−odd[i−1])∗(odd[i+k]−odd[i+k−1])
public static int numberOfSubarrays2(int[] nums, int k) {
int length = nums.length;
int[] odd = new int[length+2];
int ans = 0;
int cnt = 0;
for (int i = 0; i < length; i++) {
if(nums[i]%2 == 1){
odd[++cnt] = i ;
}
}
odd[0] = -1;
odd[++cnt] = length;
for (int i = 1; i +k <= cnt ; i++) {
ans += (odd[i]-odd[i-1])*(odd[i+k] - odd[i+k-1]);
}
return ans;
}
//45627,50891,94884,11286,35337,46414,62029,20247,72789,89158,54203,79628,25920,16832,47469,80909
public static void main(String[] args) {
int i = numberOfSubarrays2(new int[]{1,1,2,1,1}, 3);
System.out.println(i);
}
}
java面試題
- ThreadLocal 是什麼?有哪些使用場景?
線程局部變量是侷限於線程內部的變量,屬於線程自身所有,不在多個線程間共享。Java提供ThreadLocal類來支持線程局部變量,是一種實現線程安全的方式。但是在管理環境下(如 web 服務器)使用線程局部變量的時候要特別小心,在這種情況下,工作線程的生命週期比任何應用變量的生命週期都要長。任何線程局部變量一旦在工作完成後沒有釋放,Java 應用就存在內存泄露的風險。
52.說一下 synchronized 底層實現原理?
synchronized可以保證方法或者代碼塊在運行時,同一時刻只有一個方法可以進入到臨界區,同時它還可以保證共享變量的內存可見性。
Java中每一個對象都可以作爲鎖,這是synchronized實現同步的基礎:
- 普通同步方法,鎖是當前實例對象
- 靜態同步方法,鎖是當前類的class對象
- 同步方法塊,鎖是括號裏面的對象
- synchronized 和 volatile 的區別是什麼?
- volatile本質是在告訴jvm當前變量在寄存器(工作內存)中的值是不確定的,需要從主存中讀取;synchronized則是鎖定當前變量,只有當前線程可以訪問該變量,其他線程被阻塞住。
- volatile僅能使用在變量級別;synchronized則可以使用在變量、方法、和類級別的
- volatile僅能實現變量的修改可見性,不能保證原子性;而synchronized則可以保證變量的修改可見性和原子性。
- volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞。
- volatile標記的變量不會被編譯器優化;synchronized標記的變量可以被編譯器優化。
- synchronized 和 Lock 有什麼區別?
- 首先synchronized是java內置關鍵字,在jvm層面,Lock是個java類;
- synchronized無法判斷是否獲取鎖的狀態,Lock可以判斷是否獲取到鎖;
- synchronized會自動釋放鎖(a 線程執行完同步代碼會釋放鎖 ;b 線程執行過程中發生異常會釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),否則容易造成線程死鎖;
- 用synchronized關鍵字的兩個線程1和線程2,如果當前線程1獲得鎖,線程2線程等待。如果線程1阻塞,線程2則會一直等待下去,而Lock鎖就不一定會等待下去,如果嘗試獲取不到鎖,線程可以不用一直等待就結束了;
- synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(兩者皆可);
- Lock鎖適合大量同步的代碼的同步問題,synchronized鎖適合代碼少量的同步問題。
- synchronized 和 ReentrantLock 區別是什麼?
synchronized是和if、else、for、while一樣的關鍵字,ReentrantLock是類,這是二者的本質區別。既然ReentrantLock是類,那麼它就提供了比synchronized更多更靈活的特性,可以被繼承、可以有方法、可以有各種各樣的類變量,ReentrantLock比synchronized的擴展性體現在幾點上:
- ReentrantLock可以對獲取鎖的等待時間進行設置,這樣就避免了死鎖
- ReentrantLock可以獲取各種鎖的信息
- ReentrantLock可以靈活地實現多路通知
另外,二者的鎖機制其實也是不一樣的:ReentrantLock底層調用的是Unsafe的park方法加鎖,synchronized操作的應該是對象頭中mark word。
- 說一下 atomic 的原理?
Atomic包中的類基本的特性就是在多線程環境下,當有多個線程同時對單個(包括基本類型及引用類型)變量進行操作時,具有排他性,即當多個線程同時對該變量的值進行更新時,僅有一個線程能成功,而未成功的線程可以向自旋鎖一樣,繼續嘗試,一直等到執行成功。
Atomic系列的類中的核心方法都會調用unsafe類中的幾個本地方法。我們需要先知道一個東西就是Unsafe類,全名爲:sun.misc.Unsafe,這個類包含了大量的對C代碼的操作,包括很多直接內存分配以及原子操作的調用,而它之所以標記爲非安全的,是告訴你這個裏面大量的方法調用都會存在安全隱患,需要小心使用,否則會導致嚴重的後果,例如在通過unsafe分配內存的時候,如果自己指定某些區域可能會導致一些類似C++一樣的指針越界到其他進程的問題。 - 什麼是反射?
反射主要是指程序可以訪問、檢測和修改它本身狀態或行爲的一種能力
Java反射:
在Java運行時環境中,對於任意一個類,能否知道這個類有哪些屬性和方法?對於任意一個對象,能否調用它的任意一個方法
Java反射機制主要提供了以下功能:
- 在運行時判斷任意一個對象所屬的類。
- 在運行時構造任意一個類的對象。
- 在運行時判斷任意一個類所具有的成員變量和方法。
- 在運行時調用任意一個對象的方法。
- 什麼是 java 序列化?什麼情況下需要序列化?
簡單說就是爲了保存在內存中的各種對象的狀態(也就是實例變量,不是方法),並且可以把保存的對象狀態再讀出來。雖然你可以用你自己的各種各樣的方法來保存object states,但是Java給你提供一種應該比你自己好的保存對象狀態的機制,那就是序列化。
什麼情況下需要序列化:
a)當你想把的內存中的對象狀態保存到一個文件中或者數據庫中時候;
b)當你想用套接字在網絡上傳送對象的時候;
c)當你想通過RMI傳輸對象的時候; - 動態代理是什麼?有哪些應用?
動態代理:
當想要給實現了某個接口的類中的方法,加一些額外的處理。比如說加日誌,加事務等。可以給這個類創建一個代理,故名思議就是創建一個新的類,這個類不僅包含原來類方法的功能,而且還在原來的基礎上添加了額外處理的新類。這個代理類並不是定義好的,是動態生成的。具有解耦意義,靈活,擴展性強。
動態代理的應用:
- Spring的AOP
- 加事務
- 加權限
- 加日誌
- 怎麼實現動態代理?
- JDK動態代理
首先必須定義一個接口,還要有一個InvocationHandler(將實現接口的類的對象傳遞給它)處理類。再有一個工具類Proxy(習慣性將其稱爲代理類,因爲調用他的newInstance()可以產生代理對象,其實他只是一個產生代理對象的工具類)。利用到InvocationHandler,拼接代理類源碼,將其編譯生成代理類的二進制碼,利用加載器加載,並將其實例化產生代理對象,最後返回。
public interface People {
public void sayHello();
}
public class Chinese implements People {
@Override
public void sayHello() {
System.out.println("Chinese say hello.");
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class PeopleInvocationHandler implements InvocationHandler{
private Object peolple;
Intermediary(Object people){
this.people = people;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object invoke = method.invoke(people, args);
System.out.println("-------- end ---------");
return invoke;
}
}
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
People chinese = new People();
PeopleInvocationHandler invocationHandler = new PeopleInvocationHandler(chinese);
People proxy = (People) Proxy.newProxyInstance(chinese.getClass().getClassLoader(), chinese.getClass().getInterfaces(), invocationHandler);
proxy.sayHello();
}
}
- CGLIB 代理
public class Chinese {
public void sayHello(){
System.out.println("Chinese say hello");
}
}
import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class ChinesePoxy implements MethodInterceptor {
@Override
public Object intercept(Object object, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {
Object intercept = methodProxy.invokeSuper(object, args);
System.out.println("-------- end ---------");
return intercept;
}
}
import net.sf.cglib.proxy.Enhancer;
public class Test {
public static void main(String[] args) {
ChineseProxy chineseProxy = new ChineseProxy();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Chinese.class);
enhancer.setCallback(chineseProxy);
Chinese proxy = (Chinese) enhancer.create();
proxy.sayHello();
}
}
- 爲什麼要使用克隆?
想對一個對象進行處理,又想保留原有的數據進行接下來的操作,就需要克隆了,Java語言中克隆針對的是類的實例。 - 如何實現對象克隆?
1). 實現Cloneable接口並重寫Object類中的clone()方法;
2). 實現Serializable接口,通過對象的序列化和反序列化實現克隆,可以實現真正的深度克隆,代碼如下:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class MyUtil {
private MyUtil() {
throw new AssertionError();
}
@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T obj) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bout);
oos.writeObject(obj);
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bin);
return (T) ois.readObject();
// 說明:調用ByteArrayInputStream或ByteArrayOutputStream對象的close方法沒有任何意義
// 這兩個基於內存的流只要垃圾回收器清理對象就能夠釋放資源,這一點不同於對外部資源(如文件流)的釋放
}
}
下面是測試代碼:
import java.io.Serializable;
/**
* 人類
* @author nnngu
*/
class Person implements Serializable {
private static final long serialVersionUID = -9102017020286042305L;
private String name; // 姓名
private int age; // 年齡
private Car car; // 座駕
public Person(String name, int age, Car car) {
this.name = name;
this.age = age;
this.car = car;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";
}
}
/**
* 小汽車類
* @author nnngu
*
*/
class Car implements Serializable {
private static final long serialVersionUID = -5713945027627603702L;
private String brand; // 品牌
private int maxSpeed; // 最高時速
public Car(String brand, int maxSpeed) {
this.brand = brand;
this.maxSpeed = maxSpeed;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getMaxSpeed() {
return maxSpeed;
}
public void setMaxSpeed(int maxSpeed) {
this.maxSpeed = maxSpeed;
}
@Override
public String toString() {
return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";
}
}
class CloneTest {
public static void main(String[] args) {
try {
Person p1 = new Person("郭靖", 33, new Car("Benz", 300));
Person p2 = MyUtil.clone(p1); // 深度克隆
p2.getCar().setBrand("BYD");
// 修改克隆的Person對象p2關聯的汽車對象的品牌屬性
// 原來的Person對象p1關聯的汽車不會受到任何影響
// 因爲在克隆Person對象時其關聯的汽車對象也被克隆了
System.out.println(p1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意:基於序列化和反序列化實現的克隆不僅僅是深度克隆,更重要的是通過泛型限定,可以檢查出要克隆的對象是否支持序列化,這項檢查是編譯器完成的,不是在運行時拋出異常,這種是方案明顯優於使用Object類的clone方法克隆對象。讓問題在編譯的時候暴露出來總是好過把問題留到運行時。
63. 深拷貝和淺拷貝區別是什麼?
淺拷貝只是複製了對象的引用地址,兩個對象指向同一個內存地址,所以修改其中任意的值,另一個值都會隨之變化,這就是淺拷貝(例:assign())
深拷貝是將對象及值複製過來,兩個對象修改其中任意的值另一個值不會改變,這就是深拷貝(例:JSON.parse()和JSON.stringify(),但是此方法無法複製函數類型)